From e0845cfcabc5f13d2d9a6d8568926d7bc8148924 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 18 Jan 2026 20:27:24 +0300 Subject: [PATCH 01/15] Add GitHub integration service and tests Introduces a GitHubService class for interacting with the GitHub API, including methods for app installation, issue creation, and Copilot assignment. Adds related environment variables to .env.sample and type definitions to env.d.ts. Updates dependencies to include @octokit/rest, @octokit/types, and jsonwebtoken v9.0.3. Provides comprehensive tests for the new integration. --- .env.sample | 45 +++- package.json | 4 +- src/integrations/github/index.ts | 360 +++++++++++++++++++++++++++++++ src/types/env.d.ts | 44 ++++ test/integrations/github.test.ts | 359 ++++++++++++++++++++++++++++++ yarn.lock | 147 +++++++++++-- 6 files changed, 936 insertions(+), 23 deletions(-) create mode 100644 src/integrations/github/index.ts create mode 100644 test/integrations/github.test.ts diff --git a/.env.sample b/.env.sample index 2dd99082..ca88ee6f 100644 --- a/.env.sample +++ b/.env.sample @@ -51,13 +51,13 @@ GITHUB_CLIENT_ID=fakedata GITHUB_CLIENT_SECRET=fakedata ## Hawk API public url (used in OAuth to redirect to callback, should match OAuth app callback URL) -API_URL=http://127.0.0.1:4000 +API_URL=http://localhost:4000 ## Garage url -GARAGE_URL=http://127.0.0.1:8080 +GARAGE_URL=http://localhost:8080 ## Garage login url -GARAGE_LOGIN_URL=http://127.0.0.1:8080/login +GARAGE_LOGIN_URL=http://localhost:8080/login ## Hawk Catcher token from hawk.so HAWK_CATCHER_TOKEN= @@ -93,3 +93,42 @@ SSO_SP_ENTITY_ID=urn:hawk:tracker:saml ## SAML state store type (memory or redis, default: redis) SAML_STORE_TYPE=redis + +# String generated when we create GitHub App, see task-managers-integration-implementation-plan.md -> 2.1.1 +GITHUB_WEBHOOK_SECRET=623f6ed30b1f762803149893263a95cc2687fe3ce5a9f30648dcbf25712afc9e + +# Id of GitHub app +GITHUB_APP_ID=1234567 + +# GitHub App slug/name. Used to generate installation URLs +GITHUB_APP_SLUG=hawk-tracker-app; + +# Private key generated in GitHub app settings +GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA0r16047BqAxFgbltKcNOt9RGMZ2COI2ui7Ujmn9vtMV83HHu +lN/ek4kLTz8nunUc0s21xWpW7mjoaO61qSzcEn7vDhgMOAnq+wq+iJsk5IM/MJNe +sNatymMVO6Q6UuVx4Nshac3T7M8dVWx3Oc9ef9nc/dXdXTQn73EZC5pKqKoFE+yv +NGHS0JAgUGa1zDt/TqXcypz06tyrFgZFFuBo01kS1xUU/J4bpwNnk9KNq7lakdAE +JeJ/BzwTRZUzSIjtLSDjFbcjI2iXsTOasrygXDTYYcnvQf4HjRxBuCwIuGo4mOHz +86l5Icq9dNWq1Nj+FJW9IMEOYW0927uws+dotwIDAQABAoIBAHfdRitmm0eWErEm +YqzKZc+xcWtvB05bZ9gW43VQ3pyXZ3mLZARRgSuxWzlr1pD7Y7WTQ7xRy7g2+1oT +zEe5OENc52PA0dJd8cVwSwcwFz/SVvKuH8G9mYPv73fI5VOZJbibatnfNJcRBsI7 +u2SqSjm2FThbmFkW/U/3qCMtUyGy6atoUZvoeQ5aUKmW7Z4gaPVN3z+265qJwR3o +qnNUY2FNEA8tsJ05EzqBp8+SYghAiKY1QKqSb2pIfbBNmx5ItB2WeH3sTaE3Zdzy +Kk/5fDY/rlOouRg7kHQd9sPnXzy7LVhUaQG0fZdjBOpJFXHFqi1Gsf3HAMRHkhyJ ++EvgOuECgYEA/B85RCgVagwm9otXyj5JGIbj2iSAEwKnk2fxiZsGC3pirK85ZITp +bZiIvwMzCKiHlL0vjiVsFBL8tr7p5bP1g4GNTOCg2Dj+B/Or9IgxHxpDCIPrV+L0 +GBgJEsNylJESQU/xf6qBs88FLFLyRtwxXkucsfk+OrF5IBUaEdEuo9UCgYEA1ftO +wiD4O3LTs5dZiUHBrLWGejfbltHILF4oA61O7pLlMWkBvN1H8pJOI9FbTHWlv4Vc +UFApUjm4wUGArCKu7AfcSZ2+xhySIC3ilReXbQp7WqdKp/T/RaKh38zEa6MVMqAJ +cYYoWj4/NJMN1a4+G/zU5adMVb0jITyJB1EYfFsCgYEApUSSdWsRHoL4x4Rv99L8 +d2d01Po4Oj3zO11Xp6xHOh7vr+Ls7Edz/LOQcCXYvkQ7G/UnxzYgssf/gIuFJ13g +AmRaC5rz1MkHPI8umQztp3XAy0QucV4ERAb9a59S7LBsFwQgel96xjNeYL++sVSF +yBoojUGk2TSdAbrTa/qDaEECgYBCrqD5gBq7M+pjEew2CMbZEmyI07Vbh55QrTrd +AnoRgLdpsWZ4O6D7J7qwEMLZzePMDjwZTxHBbPl1R/tYKSrHpR9x1XWo+ShUXNg6 +S/LFaTnNo0pxkrimM6ssOfyP6m9lqlenB/61OKartJPgHf9+60hRFNSF933mEp5F +KHFv9wKBgQCXaVz5sgtcWgkQYSn4XTwSYKaeYww4hnIel30pASrLujQ9FubAvdjZ +apxW9AHmx4aVRrmcIPq/BlMc6lGIgx2IwMBvJVpHVeUOUMAfNMRZV0XjY715xEyW +U/uCfmCh8rfyQ75rthD4mGzNmHBpWrP/bD3c/vdj0wAxFXVyR5bG/Q== +-----END RSA PRIVATE KEY-----" + diff --git a/package.json b/package.json index a0f7db79..5789b6d5 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "test:integration:down": "docker compose -f docker-compose.test.yml down --volumes" }, "devDependencies": { + "@octokit/types": "^16.0.0", "@shelf/jest-mongodb": "^6.0.2", "@swc/core": "^1.3.0", "@types/jest": "^26.0.8", @@ -46,6 +47,7 @@ "@hawk.so/types": "^0.4.2", "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", + "@octokit/rest": "^22.0.1", "@types/amqp-connection-manager": "^2.0.4", "@types/debug": "^4.1.5", "@types/escape-html": "^1.0.0", @@ -76,7 +78,7 @@ "graphql-scalars": "^1.17.0", "graphql-type-json": "^0.3.0", "graphql-upload": "^13", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.3", "lodash": "^4.17.15", "lodash.clonedeep": "^4.5.0", "lodash.mergewith": "^4.6.2", diff --git a/src/integrations/github/index.ts b/src/integrations/github/index.ts new file mode 100644 index 00000000..9d408aa6 --- /dev/null +++ b/src/integrations/github/index.ts @@ -0,0 +1,360 @@ +import jwt from 'jsonwebtoken'; +import { Octokit } from '@octokit/rest'; +import type { Endpoints } from '@octokit/types'; + +/** + * Type for GitHub Issue creation parameters + * Extracted from Octokit types for POST /repos/{owner}/{repo}/issues + */ +export type IssueData = Pick< + Endpoints['POST /repos/{owner}/{repo}/issues']['parameters'], + 'title' | 'body' | 'labels' +>; + +/** + * Type for GitHub Issue response data + * Extracted from Octokit types for POST /repos/{owner}/{repo}/issues response + */ +export type GitHubIssue = Pick< + Endpoints['POST /repos/{owner}/{repo}/issues']['response']['data'], + 'number' | 'html_url' | 'title' | 'state' +>; + +/** + * Type for GitHub Installation response data + * Simplified version of Octokit Installation type with essential fields only + * account.login and account.type are extracted from the full GitHub account object + */ +export type Installation = { + /** + * Installation ID + */ + id: number; + + /** + * Account (user or organization) that installed the app + */ + account: { + login: string; + type: string; + }; + + /** + * Installation target type + */ + target_type: string; + + /** + * Permissions granted to the app + */ + permissions: Record; +}; + +/** + * Service for interacting with GitHub API + */ +export class GitHubService { + /** + * GitHub App ID from environment variables + */ + private readonly appId: string; + + /** + * GitHub App slug/name from environment variables + */ + private readonly appSlug: string; + + /** + * Creates an instance of GitHubService + */ + constructor() { + if (!process.env.GITHUB_APP_ID) { + throw new Error('GITHUB_APP_ID environment variable is not set'); + } + + this.appId = process.env.GITHUB_APP_ID; + this.appSlug = process.env.GITHUB_APP_SLUG || 'hawk-tracker'; + } + + /** + * Generate URL for GitHub App installation + * + * @param {string} state - State parameter for CSRF protection and context preservation. + * GitHub will return this value unchanged in the callback URL, + * allowing you to verify that the callback corresponds to the original installation request. + * Typically this is a JWT token or a random string (UUID) that serves as a key + * to retrieve stored context data (projectId, userId, etc.) from Redis or session storage. + * + * Example values: + * - JWT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0SWQiOiI2NzgiLCJ1c2VySWQiOiIxMjMiLCJ0aW1lc3RhbXAiOjE3MDk4NzY1NDB9..." + * - UUID: "550e8400-e29b-41d4-a716-446655440000" + * + * @returns {string} Installation URL with state and redirect_url parameters + */ + public getInstallationUrl(state: string): string { + if (!process.env.API_URL) { + throw new Error('API_URL environment variable must be set to generate installation URL with redirect_url'); + } + + /** + * Form callback URL based on API_URL environment variable + * This allows different callback URLs for different environments (dev, staging, production) + */ + const redirectUrl = `${process.env.API_URL}/integration/github/callback`; + + return `https://github.com/apps/${this.appSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(redirectUrl)}`; + } + + /** + * Get installation information + * + * Installation represents a GitHub App installation in a user's or organization's account. + * When a user or organization installs a GitHub App, GitHub creates an Installation object + * that links the app to the account and grants specific permissions to the app for accessing + * repositories and resources. This installation is required to generate installation access tokens + * that allow the app to make API calls on behalf of the installation. + * + * @param {string} installationId - GitHub App installation ID (unique identifier for the installation) + * @returns {Promise} Installation information containing: + * - id: Installation ID + * - account: User or organization that installed the app (with login and type) + * - target_type: Type of target (User or Organization) + * - permissions: Permissions granted to the app for this installation + * @throws {Error} If request fails + */ + public async getInstallationForRepository(installationId: string): Promise { + const token = this.createJWT(); + + /** + * Create Octokit instance with JWT authentication + */ + const octokit = new Octokit({ + auth: token, + }); + + try { + const { data } = await octokit.rest.apps.getInstallation({ + installation_id: parseInt(installationId, 10), + }); + + /** + * Extract account login and type + * account can be User (has 'login' and 'type') or Organization (has 'slug' but not 'login') + */ + let accountLogin = ''; + let accountType = ''; + + if (data.account) { + /** + * Check if account has 'login' property (User) or 'slug' property (Organization) + */ + if ('login' in data.account) { + accountLogin = data.account.login; + accountType = 'login' in data.account && 'type' in data.account ? data.account.type : 'User'; + } else if ('slug' in data.account) { + /** + * For Organization, use 'slug' as login identifier + */ + accountLogin = data.account.slug; + accountType = 'Organization'; + } + } + + return { + id: data.id, + account: { + login: accountLogin, + type: accountType, + }, + target_type: data.target_type, + permissions: data.permissions || {}, + }; + } catch (error) { + throw new Error(`Failed to get installation: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Create a GitHub issue + * + * @param {string} repoFullName - Repository full name (owner/repo) + * @param {string} installationId - GitHub App installation ID + * @param {IssueData} issueData - Issue data (title, body, labels) + * @returns {Promise} Created issue + * @throws {Error} If issue creation fails + */ + public async createIssue( + repoFullName: string, + installationId: string, + issueData: IssueData + ): Promise { + const [owner, repo] = repoFullName.split('/'); + + if (!owner || !repo) { + throw new Error(`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`); + } + + /** + * Get installation access token + */ + const accessToken = await this.createInstallationToken(installationId); + + /** + * Create Octokit instance with installation access token + */ + const octokit = new Octokit({ + auth: accessToken, + }); + + try { + const { data } = await octokit.rest.issues.create({ + owner, + repo, + title: issueData.title, + body: issueData.body, + labels: issueData.labels, + }); + + return { + number: data.number, + html_url: data.html_url, + title: data.title, + state: data.state, + }; + } catch (error) { + throw new Error(`Failed to create issue: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Assign GitHub Copilot to an issue + * + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {number} issueNumber - Issue number + * @param {string} installationId - GitHub App installation ID + * @returns {Promise} True if assignment was successful + * @throws {Error} If assignment fails + */ + public async assignCopilot( + owner: string, + repo: string, + issueNumber: number, + installationId: string + ): Promise { + /** + * Get installation access token + */ + const accessToken = await this.createInstallationToken(installationId); + + /** + * Create Octokit instance with installation access token + */ + const octokit = new Octokit({ + auth: accessToken, + }); + + try { + /** + * Assign GitHub Copilot (github-copilot[bot]) as assignee + */ + await octokit.rest.issues.addAssignees({ + owner, + repo, + issue_number: issueNumber, + assignees: ['github-copilot[bot]'], + }); + + return true; + } catch (error) { + throw new Error(`Failed to assign Copilot: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Get private key from environment variables or file + * + * @returns {string} Private key in PEM format with real newlines + * @throws {Error} If GITHUB_PRIVATE_KEY is not set + */ + private getPrivateKey(): string { + if (process.env.GITHUB_PRIVATE_KEY) { + /** + * Get private key from environment variable + * dotenv v16+ handles both multiline strings and escaped \n automatically + * But we check if there are literal \n sequences (not actual newlines) and replace them + */ + let privateKey = process.env.GITHUB_PRIVATE_KEY; + + /** + * Check if the string contains literal \n (backslash followed by n) instead of actual newlines + * This can happen if the value was stored as a single line with escaped newlines + */ + if (privateKey.includes('\\n') && !privateKey.includes('\n')) { + /** + * Replace literal \n with actual newlines + */ + privateKey = privateKey.replace(/\\n/g, '\n'); + } + + return privateKey; + } + + throw new Error('GITHUB_PRIVATE_KEY must be set'); + } + + /** + * Create JWT token for GitHub App authentication + * + * @returns {string} JWT token + */ + private createJWT(): string { + const privateKey = this.getPrivateKey(); + const now = Math.floor(Date.now() / 1000); + + /** + * JWT payload for GitHub App + * - iat: issued at time (current time) + * - exp: expiration time (10 minutes from now, GitHub allows up to 10 minutes) + * - iss: issuer (GitHub App ID) + */ + const payload = { + iat: now - 60, // Allow 1 minute clock skew + exp: now + 600, // 10 minutes expiration + iss: this.appId, + }; + + return jwt.sign(payload, privateKey, { algorithm: 'RS256' }); + } + + /** + * Get installation access token from GitHub API + * + * @param {string} installationId - GitHub App installation ID + * @returns {Promise} Installation access token (valid for 1 hour) + * @throws {Error} If token creation fails + */ + private async createInstallationToken(installationId: string): Promise { + const token = this.createJWT(); + + /** + * Create Octokit instance with JWT authentication + */ + const octokit = new Octokit({ + auth: token, + }); + + try { + /** + * Request installation access token + */ + const { data } = await octokit.rest.apps.createInstallationAccessToken({ + installation_id: parseInt(installationId, 10), + }); + + return data.token; + } catch (error) { + throw new Error(`Failed to create installation token: ${error instanceof Error ? error.message : String(error)}`); + } + } +} diff --git a/src/types/env.d.ts b/src/types/env.d.ts index d57b460f..0308f58e 100644 --- a/src/types/env.d.ts +++ b/src/types/env.d.ts @@ -11,6 +11,17 @@ declare namespace NodeJS { */ PORT: string; + /** + * Hawk API public URL + * Used in OAuth flows to redirect to callback endpoints + * Should match OAuth app callback URLs configured in GitHub App settings + * + * @example "http://localhost:4000" (local development) + * @example "https://api.stage.hawk.so" (staging) + * @example "https://api.hawk.so" (production) + */ + API_URL?: string; + /** * Metrics server port */ @@ -57,5 +68,38 @@ declare namespace NodeJS { * @example "redis://redis:6379" (Docker) or "redis://localhost:6379" (local) */ REDIS_URL?: string; + + /** + * GitHub App ID + * Unique identifier for the GitHub App + * + * @example "123456" + */ + GITHUB_APP_ID?: string; + + /** + * GitHub App private key + * PEM-encoded private key for the GitHub App + * Can be provided as multiline string or single line with \n escapes + * + * @example "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----" + */ + GITHUB_PRIVATE_KEY?: string; + + /** + * GitHub App webhook secret + * Secret used to verify webhook payloads from GitHub + * + * @example "your_webhook_secret_here" + */ + GITHUB_WEBHOOK_SECRET?: string; + + /** + * GitHub App slug/name + * Used to generate installation URLs + * + * @example "my-github-app" + */ + GITHUB_APP_SLUG?: string; } } diff --git a/test/integrations/github.test.ts b/test/integrations/github.test.ts new file mode 100644 index 00000000..32455a46 --- /dev/null +++ b/test/integrations/github.test.ts @@ -0,0 +1,359 @@ +import '../../src/env-test'; + +/** + * Mock @octokit/rest as virtual mock since module might not be installed in test environment + * Using virtual: true allows Jest to create a mock without requiring the module to exist + */ +jest.mock('@octokit/rest', () => ({ + Octokit: jest.fn(), +}), { virtual: true }); + +/** + * Mock jsonwebtoken + */ +jest.mock('jsonwebtoken'); + +import { GitHubService } from '../../src/integrations/github/index'; +import jwt from 'jsonwebtoken'; + +describe('GitHubService', () => { + let githubService: GitHubService; + const testAppId = '123456'; + const testAppSlug = 'hawk-tracker'; + const testPrivateKey = '-----BEGIN RSA PRIVATE KEY-----\nTEST_KEY\n-----END RSA PRIVATE KEY-----'; + const testInstallationId = '789012'; + const testApiUrl = 'https://api.example.com'; + + let mockOctokit: { + rest: { + apps: { + createInstallationAccessToken: jest.Mock; + getInstallation: jest.Mock; + }; + issues: { + create: jest.Mock; + addAssignees: jest.Mock; + }; + }; + }; + + const createMockOctokit = () => { + const createTokenMock = jest.fn(); + const getInstallationMock = jest.fn(); + const createIssueMock = jest.fn(); + const addAssigneesMock = jest.fn(); + + return { + rest: { + apps: { + createInstallationAccessToken: createTokenMock, + getInstallation: getInstallationMock, + }, + issues: { + create: createIssueMock, + addAssignees: addAssigneesMock, + }, + }, + }; + }; + + beforeEach(() => { + /** + * Clear all mocks + */ + jest.clearAllMocks(); + + /** + * Setup environment variables + */ + process.env.GITHUB_APP_ID = testAppId; + process.env.GITHUB_APP_SLUG = testAppSlug; + process.env.GITHUB_PRIVATE_KEY = testPrivateKey; + process.env.API_URL = testApiUrl; + + /** + * Mock Octokit instance + */ + mockOctokit = createMockOctokit(); + + /** + * Get mocked Octokit constructor and set implementation + */ + const { Octokit } = require('@octokit/rest'); + (Octokit as jest.Mock).mockImplementation(() => mockOctokit); + + /** + * Create service instance + */ + githubService = new GitHubService(); + }); + + afterEach(() => { + /** + * Clean up environment + */ + Reflect.deleteProperty(process.env, 'GITHUB_APP_ID'); + Reflect.deleteProperty(process.env, 'GITHUB_APP_SLUG'); + Reflect.deleteProperty(process.env, 'GITHUB_PRIVATE_KEY'); + Reflect.deleteProperty(process.env, 'API_URL'); + }); + + describe('getInstallationUrl', () => { + it('should generate installation URL with state and redirect_url parameters url encoded', () => { + const state = 'test-state-123'; + + const url = githubService.getInstallationUrl(state); + + expect(url).toBe( + `https://github.com/apps/${testAppSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(`${testApiUrl}/integration/github/callback`)}` + ); + }); + + it('should throw error if API_URL is not set', () => { + delete process.env.API_URL; + + const service = new GitHubService(); + + expect(() => { + service.getInstallationUrl('test-state'); + }).toThrow('API_URL environment variable must be set to generate installation URL with redirect_url'); + }); + }); + + describe('getInstallationForRepository', () => { + const mockJwtToken = 'mock-jwt-token'; + + it('should get installation information for User account', async () => { + (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + + mockOctokit.rest.apps.getInstallation.mockResolvedValue({ + data: { + id: 12345, + account: { + login: 'octocat', + type: 'User', + id: 1, + node_id: 'MDQ6VXNlcjE=', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + }, + target_type: 'User', + permissions: { + issues: 'write', + metadata: 'read', + }, + }, + } as any); + + const result = await githubService.getInstallationForRepository(testInstallationId); + + expect(result).toEqual({ + id: 12345, + account: { + login: 'octocat', + type: 'User', + }, + target_type: 'User', + permissions: { + issues: 'write', + metadata: 'read', + }, + }); + + expect(mockOctokit.rest.apps.getInstallation).toHaveBeenCalledWith({ + installation_id: parseInt(testInstallationId, 10), + }); + }); + + it('should get installation information for Organization account', async () => { + (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + + mockOctokit.rest.apps.getInstallation.mockResolvedValue({ + data: { + id: 12345, + account: { + slug: 'my-org', + type: 'Organization', + id: 1, + node_id: 'MDEyOk9yZ2FuaXphdGlvbjE=', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + }, + target_type: 'Organization', + permissions: { + issues: 'write', + metadata: 'read', + }, + }, + } as any); + + const result = await githubService.getInstallationForRepository(testInstallationId); + + expect(result).toEqual({ + id: 12345, + account: { + login: 'my-org', + type: 'Organization', + }, + target_type: 'Organization', + permissions: { + issues: 'write', + metadata: 'read', + }, + }); + }); + + it('should throw error if request fails', async () => { + (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + + mockOctokit.rest.apps.getInstallation.mockRejectedValue(new Error('Network error')); + + await expect( + githubService.getInstallationForRepository(testInstallationId) + ).rejects.toThrow('Failed to get installation'); + }); + }); + + describe('createIssue', () => { + const mockJwtToken = 'mock-jwt-token'; + const mockInstallationToken = 'mock-installation-token'; + + beforeEach(() => { + (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + + mockOctokit.rest.apps.createInstallationAccessToken.mockResolvedValue({ + data: { + token: mockInstallationToken, + expires_at: '2025-01-01T00:00:00Z', + }, + } as any); + }); + + it('should create issue successfully', async () => { + const issueData = { + title: 'Test Issue', + body: 'Test body', + labels: ['bug'], + }; + + mockOctokit.rest.issues.create.mockResolvedValue({ + data: { + number: 123, + html_url: 'https://github.com/owner/repo/issues/123', + title: 'Test Issue', + state: 'open', + }, + } as any); + + const result = await githubService.createIssue('owner/repo', testInstallationId, issueData); + + expect(result).toEqual({ + number: 123, + html_url: 'https://github.com/owner/repo/issues/123', + title: 'Test Issue', + state: 'open', + }); + + expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Test Issue', + body: 'Test body', + labels: ['bug'], + }); + }); + + it('should create issue without labels', async () => { + const issueData = { + title: 'Test Issue', + body: 'Test body', + }; + + mockOctokit.rest.issues.create.mockResolvedValue({ + data: { + number: 124, + html_url: 'https://github.com/owner/repo/issues/124', + title: 'Test Issue', + state: 'open', + }, + } as any); + + const result = await githubService.createIssue('owner/repo', testInstallationId, issueData); + + expect(result.number).toBe(124); + expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Test Issue', + body: 'Test body', + labels: undefined, + }); + }); + + it('should throw error for invalid repository name format', async () => { + const issueData = { + title: 'Test Issue', + body: 'Test body', + }; + + await expect( + githubService.createIssue('invalid-repo-name', testInstallationId, issueData) + ).rejects.toThrow('Invalid repository name format: invalid-repo-name. Expected format: owner/repo'); + }); + + it('should throw error if issue creation fails', async () => { + const issueData = { + title: 'Test Issue', + body: 'Test body', + }; + + mockOctokit.rest.issues.create.mockRejectedValue(new Error('Repository not found')); + + await expect( + githubService.createIssue('owner/repo', testInstallationId, issueData) + ).rejects.toThrow('Failed to create issue'); + }); + }); + + describe('assignCopilot', () => { + const mockJwtToken = 'mock-jwt-token'; + const mockInstallationToken = 'mock-installation-token'; + + beforeEach(() => { + (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + + mockOctokit.rest.apps.createInstallationAccessToken.mockResolvedValue({ + data: { + token: mockInstallationToken, + expires_at: '2025-01-01T00:00:00Z', + }, + } as any); + }); + + it('should assign Copilot to issue successfully', async () => { + const issueNumber = 123; + + mockOctokit.rest.issues.addAssignees.mockResolvedValue({ + data: {}, + } as any); + + const result = await githubService.assignCopilot('owner', 'repo', issueNumber, testInstallationId); + + expect(result).toBe(true); + expect(mockOctokit.rest.issues.addAssignees).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + issue_number: issueNumber, + assignees: ['github-copilot[bot]'], + }); + }); + + it('should throw error if assignment fails', async () => { + const issueNumber = 123; + + mockOctokit.rest.issues.addAssignees.mockRejectedValue(new Error('Issue not found')); + + await expect( + githubService.assignCopilot('owner', 'repo', issueNumber, testInstallationId) + ).rejects.toThrow('Failed to assign Copilot'); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 4f3e19a1..8e3c65a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -818,6 +818,100 @@ xmlbuilder "^15.1.1" xpath "^0.0.34" +"@octokit/auth-token@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-6.0.0.tgz#b02e9c08a2d8937df09a2a981f226ad219174c53" + integrity sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w== + +"@octokit/core@^7.0.6": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-7.0.6.tgz#0d58704391c6b681dec1117240ea4d2a98ac3916" + integrity sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q== + dependencies: + "@octokit/auth-token" "^6.0.0" + "@octokit/graphql" "^9.0.3" + "@octokit/request" "^10.0.6" + "@octokit/request-error" "^7.0.2" + "@octokit/types" "^16.0.0" + before-after-hook "^4.0.0" + universal-user-agent "^7.0.0" + +"@octokit/endpoint@^11.0.2": + version "11.0.2" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-11.0.2.tgz#a8d955e053a244938b81d86cd73efd2dcb5ef5af" + integrity sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ== + dependencies: + "@octokit/types" "^16.0.0" + universal-user-agent "^7.0.2" + +"@octokit/graphql@^9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-9.0.3.tgz#5b8341c225909e924b466705c13477face869456" + integrity sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA== + dependencies: + "@octokit/request" "^10.0.6" + "@octokit/types" "^16.0.0" + universal-user-agent "^7.0.0" + +"@octokit/openapi-types@^27.0.0": + version "27.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-27.0.0.tgz#374ea53781965fd02a9d36cacb97e152cefff12d" + integrity sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA== + +"@octokit/plugin-paginate-rest@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz#44dc9fff2dacb148d4c5c788b573ddc044503026" + integrity sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw== + dependencies: + "@octokit/types" "^16.0.0" + +"@octokit/plugin-request-log@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz#de1c1e557df6c08adb631bf78264fa741e01b317" + integrity sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q== + +"@octokit/plugin-rest-endpoint-methods@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz#8c54397d3a4060356a1c8a974191ebf945924105" + integrity sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw== + dependencies: + "@octokit/types" "^16.0.0" + +"@octokit/request-error@^7.0.2": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-7.1.0.tgz#440fa3cae310466889778f5a222b47a580743638" + integrity sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw== + dependencies: + "@octokit/types" "^16.0.0" + +"@octokit/request@^10.0.6": + version "10.0.7" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-10.0.7.tgz#93f619914c523750a85e7888de983e1009eb03f6" + integrity sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA== + dependencies: + "@octokit/endpoint" "^11.0.2" + "@octokit/request-error" "^7.0.2" + "@octokit/types" "^16.0.0" + fast-content-type-parse "^3.0.0" + universal-user-agent "^7.0.2" + +"@octokit/rest@^22.0.1": + version "22.0.1" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-22.0.1.tgz#4d866c32b76b711d3f736f91992e2b534163b416" + integrity sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw== + dependencies: + "@octokit/core" "^7.0.6" + "@octokit/plugin-paginate-rest" "^14.0.0" + "@octokit/plugin-request-log" "^6.0.0" + "@octokit/plugin-rest-endpoint-methods" "^17.0.0" + +"@octokit/types@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-16.0.0.tgz#fbd7fa590c2ef22af881b1d79758bfaa234dbb7c" + integrity sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg== + dependencies: + "@octokit/openapi-types" "^27.0.0" + "@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.4.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" @@ -2104,6 +2198,11 @@ basic-auth@~2.0.1: dependencies: safe-buffer "5.1.2" +before-after-hook@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-4.0.0.tgz#cf1447ab9160df6a40f3621da64d6ffc36050cb9" + integrity sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -2233,7 +2332,7 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer-equal-constant-time@1.0.1: +buffer-equal-constant-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== @@ -3397,6 +3496,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +fast-content-type-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz#5590b6c807cc598be125e6740a9fde589d2b7afb" + integrity sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg== + fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -4921,12 +5025,12 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== +jsonwebtoken@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz#6cd57ab01e9b0ac07cb847d53d3c9b6ee31f7ae2" + integrity sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g== dependencies: - jws "^3.2.2" + jws "^4.0.1" lodash.includes "^4.3.0" lodash.isboolean "^3.0.3" lodash.isinteger "^4.0.4" @@ -4935,23 +5039,23 @@ jsonwebtoken@^8.5.1: lodash.isstring "^4.0.1" lodash.once "^4.0.0" ms "^2.1.1" - semver "^5.6.0" + semver "^7.5.4" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== +jwa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804" + integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== dependencies: - buffer-equal-constant-time "1.0.1" + buffer-equal-constant-time "^1.0.1" ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== +jws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.1.tgz#07edc1be8fac20e677b283ece261498bd38f0690" + integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== dependencies: - jwa "^1.4.1" + jwa "^2.0.1" safe-buffer "^5.0.1" kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: @@ -6343,7 +6447,7 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.5.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -6360,7 +6464,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.7.3: +semver@^7.5.4, semver@^7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== @@ -7195,6 +7299,11 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^2.0.1" +universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz#c05870a58125a2dc00431f2df815a77fe69736be" + integrity sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A== + universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" From ac36e04549122bb26bf0a56b5fd73bea618d58fa Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 18 Jan 2026 21:20:44 +0300 Subject: [PATCH 02/15] Add Task Manager integration mutations and types Introduces GraphQL types and mutations for managing Task Manager integration in projects, including disconnecting and updating settings. Updates the project model to support partial updates, extends resolvers for new mutations, and adds comprehensive tests for these features. --- src/models/project.ts | 13 +- src/resolvers/project.js | 96 ++++ src/typeDefs/index.ts | 4 + src/typeDefs/project.ts | 5 + src/typeDefs/projectTaskManagerMutations.ts | 50 ++ src/typeDefs/taskManager.ts | 83 ++++ test/resolvers/project.test.ts | 514 ++++++++++++++++++++ 7 files changed, 762 insertions(+), 3 deletions(-) create mode 100644 src/typeDefs/projectTaskManagerMutations.ts create mode 100644 src/typeDefs/taskManager.ts create mode 100644 test/resolvers/project.test.ts diff --git a/src/models/project.ts b/src/models/project.ts index ad82451c..ce788e7a 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -465,9 +465,16 @@ export default class ProjectModel extends AbstractModel impleme /** * Updates project data in DataBase - * @param projectData - projectData to save - */ - public async updateProject(projectData: ProjectDBScheme): Promise { + * Performs partial update - can update one or several properties without requiring full project object + * + * @param projectData - partial project data containing one or more properties to update. + * Only provided properties will be updated; others will remain unchanged. + * Example: `{ name: 'New Name' }` will update only the name field. + * Example: `{ taskManager: null }` will remove the taskManager field. + * @returns {Promise} Updated project data + * @throws {Error} If project update fails or project not found + */ + public async updateProject(projectData: Partial): Promise { let result; try { diff --git a/src/resolvers/project.js b/src/resolvers/project.js index 636ac962..aa3cc182 100644 --- a/src/resolvers/project.js +++ b/src/resolvers/project.js @@ -391,6 +391,102 @@ module.exports = { return userModel.updateLastProjectVisit(projectId); }, + + /** + * Disconnect Task Manager integration from project + * + * @param {ResolverObj} _obj + * @param {string} projectId - project ID to disconnect Task Manager from + * @param {UserInContext} user - current authorized user {@see ../index.js} + * @param {ContextFactories} factories - factories for working with models + * + * @returns {Project} + */ + async disconnectTaskManager(_obj, { projectId }, { user, factories }) { + const project = await factories.projectsFactory.findById(projectId); + + if (!project) { + throw new ApolloError('There is no project with that id'); + } + + if (project.workspaceId.toString() === '6213b6a01e6281087467cc7a') { + throw new ApolloError('Unable to update demo project'); + } + + try { + /** + * Remove taskManager field from project + * Set updatedAt to current date + */ + return await project.updateProject({ + taskManager: null, + }); + } catch (err) { + throw new ApolloError('Failed to disconnect Task Manager', { originalError: err }); + } + }, + + /** + * Update Task Manager settings for project + * + * @param {ResolverObj} _obj + * @param {Object} input - Task Manager settings input + * @param {string} input.projectId - project ID to update + * @param {boolean} input.autoTaskEnabled - enable automatic task creation + * @param {number} input.taskThresholdTotalCount - threshold for auto task creation + * @param {boolean} input.assignAgent - assign agent (e.g. Copilot) to tasks + * @param {UserInContext} user - current authorized user {@see ../index.js} + * @param {ContextFactories} factories - factories for working with models + * + * @returns {Project} + */ + async updateTaskManagerSettings(_obj, { input }, { user, factories }) { + const { projectId, autoTaskEnabled, taskThresholdTotalCount, assignAgent } = input; + + const project = await factories.projectsFactory.findById(projectId); + + if (!project) { + throw new ApolloError('There is no project with that id'); + } + + if (project.workspaceId.toString() === '6213b6a01e6281087467cc7a') { + throw new ApolloError('Unable to update demo project'); + } + + /** + * Check if taskManager is configured + */ + if (!project.taskManager) { + throw new UserInputError('Task Manager is not configured for this project'); + } + + /** + * Validate taskThresholdTotalCount + */ + if (typeof taskThresholdTotalCount !== 'number' || taskThresholdTotalCount <= 0) { + throw new UserInputError('taskThresholdTotalCount must be a positive integer greater than 0'); + } + + try { + /** + * Update taskManager settings + * Keep existing config and usage, update only settings + */ + const updatedTaskManager = { + ...project.taskManager, + autoTaskEnabled, + taskThresholdTotalCount, + assignAgent, + updatedAt: new Date(), + }; + + return await project.updateProject({ + taskManager: updatedTaskManager, + }); + } catch (err) { + throw new ApolloError('Failed to update Task Manager settings', { originalError: err }); + } + }, }, Project: { /** diff --git a/src/typeDefs/index.ts b/src/typeDefs/index.ts index 49b1a7ca..8eca3893 100644 --- a/src/typeDefs/index.ts +++ b/src/typeDefs/index.ts @@ -9,6 +9,8 @@ import projectNotificationsMutations from './projectNotificationsMutations'; import projectEventGroupingPattern from './projectEventGroupingPattern'; import projectEventGroupingPatternMutations from './projectEventGroupingPatternMutations'; import project from './project'; +import taskManager from './taskManager'; +import projectTaskManagerMutations from './projectTaskManagerMutations'; import user from './user'; import userNotifications from './userNotifications'; import userNotificationsMutations from './userNotificationsMutations'; @@ -93,6 +95,8 @@ const typeDefinitions = [ projectNotifications, projectNotificationsMutations, project, + taskManager, + projectTaskManagerMutations, user, userNotifications, userNotificationsMutations, diff --git a/src/typeDefs/project.ts b/src/typeDefs/project.ts index 8ee70f92..296beb41 100644 --- a/src/typeDefs/project.ts +++ b/src/typeDefs/project.ts @@ -402,6 +402,11 @@ type Project { Detailed info for a specific release """ releaseDetails(release: String!): ProjectReleaseDetails! + + """ + Task Manager integration configuration + """ + taskManager: TaskManager } extend type Query { diff --git a/src/typeDefs/projectTaskManagerMutations.ts b/src/typeDefs/projectTaskManagerMutations.ts new file mode 100644 index 00000000..eb9abf1b --- /dev/null +++ b/src/typeDefs/projectTaskManagerMutations.ts @@ -0,0 +1,50 @@ +import { gql } from 'apollo-server-express'; + +export default gql` + """ + Input type for updating task manager settings + """ + input UpdateTaskManagerSettingsInput { + """ + Project ID to update + """ + projectId: ID! + + """ + Enable automatic task creation by scheduled worker + """ + autoTaskEnabled: Boolean! + + """ + Threshold for auto task creation (minimum totalCount) + """ + taskThresholdTotalCount: Int! + + """ + Assign agent (e.g. Copilot) to newly created tasks + """ + assignAgent: Boolean! + } + + extend type Mutation { + """ + Disconnect Task Manager integration from project + """ + disconnectTaskManager( + """ + Project ID to disconnect Task Manager from + """ + projectId: ID! + ): Project! @requireAdmin + + """ + Update Task Manager settings for project + """ + updateTaskManagerSettings( + """ + Task Manager settings input + """ + input: UpdateTaskManagerSettingsInput! + ): Project! @requireAdmin + } +`; diff --git a/src/typeDefs/taskManager.ts b/src/typeDefs/taskManager.ts new file mode 100644 index 00000000..518b20dd --- /dev/null +++ b/src/typeDefs/taskManager.ts @@ -0,0 +1,83 @@ +import { gql } from 'apollo-server-express'; + +export default gql` + """ + Task Manager usage tracking for daily auto-task budget + """ + type TaskManagerUsage { + """ + UTC day boundary (e.g. 2026-01-17T00:00:00.000Z) + """ + dayStartUtc: DateTime! + + """ + Number of auto-created tasks since dayStartUtc + """ + autoTasksCreated: Int! + } + + """ + GitHub Task Manager specific configuration + """ + type GitHubTaskManagerConfig { + """ + GitHub App installation ID + """ + installationId: String! + + """ + Repository ID + """ + repoId: String! + + """ + Repository full name (owner/repo) + """ + repoFullName: String! + } + + """ + Task Manager configuration for project + """ + type TaskManager { + """ + Type of task manager (currently only 'github' is supported) + """ + type: String! + + """ + Enable automatic task creation by scheduled worker + """ + autoTaskEnabled: Boolean! + + """ + Threshold for auto task creation (minimum totalCount) + """ + taskThresholdTotalCount: Int! + + """ + Assign agent (e.g. Copilot) to newly created tasks + """ + assignAgent: Boolean! + + """ + Usage tracking for daily auto-task budget + """ + usage: TaskManagerUsage + + """ + Date when integration was connected + """ + connectedAt: DateTime! + + """ + Date when configuration was last updated + """ + updatedAt: DateTime! + + """ + Task manager specific configuration (typed by type) + """ + config: GitHubTaskManagerConfig! + } +`; diff --git a/test/resolvers/project.test.ts b/test/resolvers/project.test.ts new file mode 100644 index 00000000..d9dcad87 --- /dev/null +++ b/test/resolvers/project.test.ts @@ -0,0 +1,514 @@ +import '../../src/env-test'; +import { ObjectId } from 'mongodb'; +import { ProjectDBScheme } from '@hawk.so/types'; + +/** + * Task Manager configuration type (matching ProjectTaskManagerConfig from @hawk.so/types) + */ +type ProjectTaskManagerConfig = { + type: 'github'; + autoTaskEnabled: boolean; + taskThresholdTotalCount: number; + assignAgent: boolean; + usage?: { + dayStartUtc: Date; + autoTasksCreated: number; + }; + connectedAt: Date; + updatedAt: Date; + config: { + installationId: string; + repoId: string | number; + repoFullName: string; + }; +}; +// eslint-disable-next-line @typescript-eslint/no-require-imports +const projectResolver = require('../../src/resolvers/project'); +import { ResolverContextWithUser } from '../../src/types/graphql'; +import { ApolloError, UserInputError } from 'apollo-server-express'; + +// Set environment variables for test +process.env.JWT_SECRET_ACCESS_TOKEN = 'belarus'; +process.env.JWT_SECRET_REFRESH_TOKEN = 'abacaba'; +process.env.JWT_SECRET_PROJECT_TOKEN = 'qwerty'; + +/** + * Demo workspace ID (projects in this workspace cannot be updated) + */ +const DEMO_WORKSPACE_ID = '6213b6a01e6281087467cc7a'; + +/** + * Creates mock project with optional taskManager configuration + */ +function createMockProject(options: { + projectId?: string; + workspaceId?: string; + taskManager?: ProjectTaskManagerConfig | null; +}): Partial & { + _id: ObjectId; + workspaceId: ObjectId; + updateProject: jest.Mock; + taskManager?: ProjectTaskManagerConfig | null; +} { + const { + projectId = new ObjectId().toString(), + workspaceId = new ObjectId().toString(), + taskManager, + } = options; + + const mockUpdateProject = jest.fn().mockImplementation(async (data) => { + /** + * Return updated project with new data + */ + return { + _id: new ObjectId(projectId), + workspaceId: new ObjectId(workspaceId), + name: 'Test Project', + integrationId: 'test-integration-id', + token: 'test-token', + notifications: [], + eventGroupingPatterns: [], + taskManager: data.taskManager !== undefined ? data.taskManager : taskManager, + }; + }); + + return { + _id: new ObjectId(projectId), + workspaceId: new ObjectId(workspaceId), + name: 'Test Project', + integrationId: 'test-integration-id', + token: 'test-token', + notifications: [], + eventGroupingPatterns: [], + taskManager, + updateProject: mockUpdateProject, + }; +} + +/** + * Creates test context with mocked factories + */ +function createMockContext(mockProject: ReturnType): ResolverContextWithUser { + const userId = new ObjectId().toString(); + + const mockProjectsFactory = { + findById: jest.fn().mockResolvedValue(mockProject), + }; + + return { + user: { + id: userId, + accessTokenExpired: false, + }, + factories: { + projectsFactory: mockProjectsFactory as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }, + }; +} + +describe('Project Resolver - Task Manager Mutations', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('disconnectTaskManager', () => { + const mockTaskManagerConfig: ProjectTaskManagerConfig = { + type: 'github', + autoTaskEnabled: true, + taskThresholdTotalCount: 10, + assignAgent: false, + connectedAt: new Date('2025-01-01'), + updatedAt: new Date('2025-01-01'), + config: { + installationId: '123456', + repoId: '789012', + repoFullName: 'owner/repo', + }, + }; + + it('should disconnect Task Manager successfully', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + const result = await projectResolver.Mutation.disconnectTaskManager( + {}, + { projectId: mockProject._id.toString() }, + context + ); + + expect(context.factories.projectsFactory.findById).toHaveBeenCalledWith(mockProject._id.toString()); + expect(mockProject.updateProject).toHaveBeenCalledWith({ + taskManager: null, + }); + expect(result.taskManager).toBeNull(); + }); + + it('should throw error if project not found', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + (context.factories.projectsFactory.findById as jest.Mock).mockResolvedValue(null); + + await expect( + projectResolver.Mutation.disconnectTaskManager( + {}, + { projectId: mockProject._id.toString() }, + context + ) + ).rejects.toThrow(ApolloError); + }); + + it('should throw error for demo project', async () => { + const mockProject = createMockProject({ + workspaceId: DEMO_WORKSPACE_ID, + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + await expect( + projectResolver.Mutation.disconnectTaskManager( + {}, + { projectId: mockProject._id.toString() }, + context + ) + ).rejects.toThrow('Unable to update demo project'); + }); + + it('should handle updateProject errors gracefully', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + /** + * Reset mock and configure to reject with an error + */ + mockProject.updateProject.mockReset(); + mockProject.updateProject.mockRejectedValue(new Error('Database error')); + + try { + await projectResolver.Mutation.disconnectTaskManager( + {}, + { projectId: mockProject._id.toString() }, + context + ); + fail('Expected ApolloError to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(ApolloError); + expect((error as ApolloError).message).toBe('Failed to disconnect Task Manager'); + } + }); + + it('should work even if taskManager is already null', async () => { + const mockProject = createMockProject({ + taskManager: null, + }); + const context = createMockContext(mockProject); + + const result = await projectResolver.Mutation.disconnectTaskManager( + {}, + { projectId: mockProject._id.toString() }, + context + ); + + expect(mockProject.updateProject).toHaveBeenCalledWith({ + taskManager: null, + }); + expect(result.taskManager).toBeNull(); + }); + }); + + describe('updateTaskManagerSettings', () => { + const mockTaskManagerConfig: ProjectTaskManagerConfig = { + type: 'github', + autoTaskEnabled: false, + taskThresholdTotalCount: 5, + assignAgent: false, + connectedAt: new Date('2025-01-01'), + updatedAt: new Date('2025-01-01'), + config: { + installationId: '123456', + repoId: '789012', + repoFullName: 'owner/repo', + }, + }; + + it('should update Task Manager settings successfully', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 20, + assignAgent: true, + }; + + const result = await projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ); + + expect(context.factories.projectsFactory.findById).toHaveBeenCalledWith(mockProject._id.toString()); + expect(mockProject.updateProject).toHaveBeenCalledWith({ + taskManager: { + ...mockTaskManagerConfig, + autoTaskEnabled: true, + taskThresholdTotalCount: 20, + assignAgent: true, + updatedAt: expect.any(Date), + }, + }); + expect(result).toBeDefined(); + }); + + it('should throw error if project not found', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + (context.factories.projectsFactory.findById as jest.Mock).mockResolvedValue(null); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 20, + assignAgent: true, + }; + + await expect( + projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ) + ).rejects.toThrow(ApolloError); + }); + + it('should throw error for demo project', async () => { + const mockProject = createMockProject({ + workspaceId: DEMO_WORKSPACE_ID, + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 20, + assignAgent: true, + }; + + await expect( + projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ) + ).rejects.toThrow('Unable to update demo project'); + }); + + it('should throw error if taskManager is not configured', async () => { + const mockProject = createMockProject({ + taskManager: null, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 20, + assignAgent: true, + }; + + await expect( + projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ) + ).rejects.toThrow(UserInputError); + }); + + it('should throw error if taskThresholdTotalCount is not positive', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 0, + assignAgent: true, + }; + + await expect( + projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ) + ).rejects.toThrow(UserInputError); + }); + + it('should throw error if taskThresholdTotalCount is negative', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: -5, + assignAgent: true, + }; + + await expect( + projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ) + ).rejects.toThrow(UserInputError); + }); + + it('should throw error if taskThresholdTotalCount is not a number', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 'not-a-number' as any, + assignAgent: true, + }; + + await expect( + projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ) + ).rejects.toThrow(UserInputError); + }); + + it('should preserve existing taskManager config and usage when updating settings', async () => { + const mockTaskManagerWithUsage: ProjectTaskManagerConfig = { + ...mockTaskManagerConfig, + usage: { + dayStartUtc: new Date('2025-01-17T00:00:00.000Z'), + autoTasksCreated: 5, + }, + }; + + const mockProject = createMockProject({ + taskManager: mockTaskManagerWithUsage, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 15, + assignAgent: true, + }; + + await projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ); + + expect(mockProject.updateProject).toHaveBeenCalledWith({ + taskManager: { + ...mockTaskManagerWithUsage, + autoTaskEnabled: true, + taskThresholdTotalCount: 15, + assignAgent: true, + updatedAt: expect.any(Date), + }, + }); + }); + + it('should handle updateProject errors gracefully', async () => { + const mockProject = createMockProject({ + taskManager: mockTaskManagerConfig, + }); + const context = createMockContext(mockProject); + + /** + * Reset mock and configure to reject with an error + */ + mockProject.updateProject.mockReset(); + mockProject.updateProject.mockRejectedValue(new Error('Database error')); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 20, + assignAgent: true, + }; + + try { + await projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ); + fail('Expected ApolloError to be thrown'); + } catch (error) { + expect(error).toBeInstanceOf(ApolloError); + expect((error as ApolloError).message).toBe('Failed to update Task Manager settings'); + } + }); + + it('should update only provided settings and preserve others', async () => { + const mockProject = createMockProject({ + taskManager: { + ...mockTaskManagerConfig, + autoTaskEnabled: false, + assignAgent: false, + }, + }); + const context = createMockContext(mockProject); + + const input = { + projectId: mockProject._id.toString(), + autoTaskEnabled: true, + taskThresholdTotalCount: 30, + assignAgent: true, + }; + + await projectResolver.Mutation.updateTaskManagerSettings( + {}, + { input }, + context + ); + + const updateCall = mockProject.updateProject.mock.calls[0][0]; + expect(updateCall.taskManager.autoTaskEnabled).toBe(true); + expect(updateCall.taskManager.taskThresholdTotalCount).toBe(30); + expect(updateCall.taskManager.assignAgent).toBe(true); + expect(updateCall.taskManager.config).toEqual(mockTaskManagerConfig.config); + expect(updateCall.taskManager.type).toBe('github'); + expect(updateCall.taskManager.connectedAt).toEqual(mockTaskManagerConfig.connectedAt); + }); + }); +}); From d60f2beeec6d603890800c3b125b7bc1127a5873 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 18 Jan 2026 22:41:46 +0300 Subject: [PATCH 03/15] Add GitHub integration with installation flow Introduces GitHub integration endpoints and service, including a Redis-backed state store for secure installation flow. Refactors integration code into modular files, appends GitHub routes to the Express app, and updates tests to import the service from its new location. --- package.json | 2 +- src/index.ts | 17 +- src/integrations/github/index.ts | 364 +----------------- src/integrations/github/routes.ts | 149 +++++++ src/integrations/github/service.ts | 360 +++++++++++++++++ .../store/InstallStateStoreInterface.ts | 44 +++ .../github/store/install-state.redis.store.ts | 120 ++++++ test/integrations/github.test.ts | 2 +- 8 files changed, 699 insertions(+), 359 deletions(-) create mode 100644 src/integrations/github/routes.ts create mode 100644 src/integrations/github/service.ts create mode 100644 src/integrations/github/store/InstallStateStoreInterface.ts create mode 100644 src/integrations/github/store/install-state.redis.store.ts diff --git a/package.json b/package.json index 5789b6d5..77bf4fac 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "test:integration:down": "docker compose -f docker-compose.test.yml down --volumes" }, "devDependencies": { - "@octokit/types": "^16.0.0", "@shelf/jest-mongodb": "^6.0.2", "@swc/core": "^1.3.0", "@types/jest": "^26.0.8", @@ -48,6 +47,7 @@ "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", "@octokit/rest": "^22.0.1", + "@octokit/types": "^16.0.0", "@types/amqp-connection-manager": "^2.0.4", "@types/debug": "^4.1.5", "@types/escape-html": "^1.0.0", diff --git a/src/index.ts b/src/index.ts index 98f7a62e..14b18804 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { requestLogger } from './utils/logger'; import ReleasesFactory from './models/releasesFactory'; import RedisHelper from './redisHelper'; import { appendSsoRoutes } from './sso'; +import { appendGitHubRoutes } from './integrations/github'; /** * Option to enable playground @@ -248,20 +249,26 @@ class HawkAPI { await redis.initialize(); /** - * Setup shared factories for SSO routes - * SSO endpoints don't require per-request DataLoaders isolation, + * Setup shared factories for SSO and GitHub integration routes + * These endpoints don't require per-request DataLoaders isolation, * so we can reuse the same factories instance * Created here to avoid duplication with createContext */ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const ssoDataLoaders = new DataLoaders(mongo.databases.hawk!); - const ssoFactories = HawkAPI.setupFactories(ssoDataLoaders); + const sharedDataLoaders = new DataLoaders(mongo.databases.hawk!); + const sharedFactories = HawkAPI.setupFactories(sharedDataLoaders); /** * Append SSO routes to Express app using shared factories * Note: This must be called after database connections are established */ - appendSsoRoutes(this.app, ssoFactories); + appendSsoRoutes(this.app, sharedFactories); + + /** + * Append GitHub integration routes to Express app using shared factories + * Note: This must be called after database connections are established + */ + appendGitHubRoutes(this.app, sharedFactories); await this.server.start(); this.app.use(graphqlUploadExpress()); diff --git a/src/integrations/github/index.ts b/src/integrations/github/index.ts index 9d408aa6..b2fedfa2 100644 --- a/src/integrations/github/index.ts +++ b/src/integrations/github/index.ts @@ -1,360 +1,20 @@ -import jwt from 'jsonwebtoken'; -import { Octokit } from '@octokit/rest'; -import type { Endpoints } from '@octokit/types'; +import express from 'express'; +import { createGitHubRouter } from './routes'; +import { ContextFactories } from '../../types/graphql'; /** - * Type for GitHub Issue creation parameters - * Extracted from Octokit types for POST /repos/{owner}/{repo}/issues + * Re-export types and service from service.ts for backward compatibility */ -export type IssueData = Pick< - Endpoints['POST /repos/{owner}/{repo}/issues']['parameters'], - 'title' | 'body' | 'labels' ->; +export { GitHubService, IssueData, GitHubIssue, Installation } from './service'; /** - * Type for GitHub Issue response data - * Extracted from Octokit types for POST /repos/{owner}/{repo}/issues response + * Append GitHub routes to Express app + * + * @param app - Express application instance + * @param factories - context factories for database access */ -export type GitHubIssue = Pick< - Endpoints['POST /repos/{owner}/{repo}/issues']['response']['data'], - 'number' | 'html_url' | 'title' | 'state' ->; +export function appendGitHubRoutes(app: express.Application, factories: ContextFactories): void { + const githubRouter = createGitHubRouter(factories); -/** - * Type for GitHub Installation response data - * Simplified version of Octokit Installation type with essential fields only - * account.login and account.type are extracted from the full GitHub account object - */ -export type Installation = { - /** - * Installation ID - */ - id: number; - - /** - * Account (user or organization) that installed the app - */ - account: { - login: string; - type: string; - }; - - /** - * Installation target type - */ - target_type: string; - - /** - * Permissions granted to the app - */ - permissions: Record; -}; - -/** - * Service for interacting with GitHub API - */ -export class GitHubService { - /** - * GitHub App ID from environment variables - */ - private readonly appId: string; - - /** - * GitHub App slug/name from environment variables - */ - private readonly appSlug: string; - - /** - * Creates an instance of GitHubService - */ - constructor() { - if (!process.env.GITHUB_APP_ID) { - throw new Error('GITHUB_APP_ID environment variable is not set'); - } - - this.appId = process.env.GITHUB_APP_ID; - this.appSlug = process.env.GITHUB_APP_SLUG || 'hawk-tracker'; - } - - /** - * Generate URL for GitHub App installation - * - * @param {string} state - State parameter for CSRF protection and context preservation. - * GitHub will return this value unchanged in the callback URL, - * allowing you to verify that the callback corresponds to the original installation request. - * Typically this is a JWT token or a random string (UUID) that serves as a key - * to retrieve stored context data (projectId, userId, etc.) from Redis or session storage. - * - * Example values: - * - JWT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0SWQiOiI2NzgiLCJ1c2VySWQiOiIxMjMiLCJ0aW1lc3RhbXAiOjE3MDk4NzY1NDB9..." - * - UUID: "550e8400-e29b-41d4-a716-446655440000" - * - * @returns {string} Installation URL with state and redirect_url parameters - */ - public getInstallationUrl(state: string): string { - if (!process.env.API_URL) { - throw new Error('API_URL environment variable must be set to generate installation URL with redirect_url'); - } - - /** - * Form callback URL based on API_URL environment variable - * This allows different callback URLs for different environments (dev, staging, production) - */ - const redirectUrl = `${process.env.API_URL}/integration/github/callback`; - - return `https://github.com/apps/${this.appSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(redirectUrl)}`; - } - - /** - * Get installation information - * - * Installation represents a GitHub App installation in a user's or organization's account. - * When a user or organization installs a GitHub App, GitHub creates an Installation object - * that links the app to the account and grants specific permissions to the app for accessing - * repositories and resources. This installation is required to generate installation access tokens - * that allow the app to make API calls on behalf of the installation. - * - * @param {string} installationId - GitHub App installation ID (unique identifier for the installation) - * @returns {Promise} Installation information containing: - * - id: Installation ID - * - account: User or organization that installed the app (with login and type) - * - target_type: Type of target (User or Organization) - * - permissions: Permissions granted to the app for this installation - * @throws {Error} If request fails - */ - public async getInstallationForRepository(installationId: string): Promise { - const token = this.createJWT(); - - /** - * Create Octokit instance with JWT authentication - */ - const octokit = new Octokit({ - auth: token, - }); - - try { - const { data } = await octokit.rest.apps.getInstallation({ - installation_id: parseInt(installationId, 10), - }); - - /** - * Extract account login and type - * account can be User (has 'login' and 'type') or Organization (has 'slug' but not 'login') - */ - let accountLogin = ''; - let accountType = ''; - - if (data.account) { - /** - * Check if account has 'login' property (User) or 'slug' property (Organization) - */ - if ('login' in data.account) { - accountLogin = data.account.login; - accountType = 'login' in data.account && 'type' in data.account ? data.account.type : 'User'; - } else if ('slug' in data.account) { - /** - * For Organization, use 'slug' as login identifier - */ - accountLogin = data.account.slug; - accountType = 'Organization'; - } - } - - return { - id: data.id, - account: { - login: accountLogin, - type: accountType, - }, - target_type: data.target_type, - permissions: data.permissions || {}, - }; - } catch (error) { - throw new Error(`Failed to get installation: ${error instanceof Error ? error.message : String(error)}`); - } - } - - /** - * Create a GitHub issue - * - * @param {string} repoFullName - Repository full name (owner/repo) - * @param {string} installationId - GitHub App installation ID - * @param {IssueData} issueData - Issue data (title, body, labels) - * @returns {Promise} Created issue - * @throws {Error} If issue creation fails - */ - public async createIssue( - repoFullName: string, - installationId: string, - issueData: IssueData - ): Promise { - const [owner, repo] = repoFullName.split('/'); - - if (!owner || !repo) { - throw new Error(`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`); - } - - /** - * Get installation access token - */ - const accessToken = await this.createInstallationToken(installationId); - - /** - * Create Octokit instance with installation access token - */ - const octokit = new Octokit({ - auth: accessToken, - }); - - try { - const { data } = await octokit.rest.issues.create({ - owner, - repo, - title: issueData.title, - body: issueData.body, - labels: issueData.labels, - }); - - return { - number: data.number, - html_url: data.html_url, - title: data.title, - state: data.state, - }; - } catch (error) { - throw new Error(`Failed to create issue: ${error instanceof Error ? error.message : String(error)}`); - } - } - - /** - * Assign GitHub Copilot to an issue - * - * @param {string} owner - Repository owner - * @param {string} repo - Repository name - * @param {number} issueNumber - Issue number - * @param {string} installationId - GitHub App installation ID - * @returns {Promise} True if assignment was successful - * @throws {Error} If assignment fails - */ - public async assignCopilot( - owner: string, - repo: string, - issueNumber: number, - installationId: string - ): Promise { - /** - * Get installation access token - */ - const accessToken = await this.createInstallationToken(installationId); - - /** - * Create Octokit instance with installation access token - */ - const octokit = new Octokit({ - auth: accessToken, - }); - - try { - /** - * Assign GitHub Copilot (github-copilot[bot]) as assignee - */ - await octokit.rest.issues.addAssignees({ - owner, - repo, - issue_number: issueNumber, - assignees: ['github-copilot[bot]'], - }); - - return true; - } catch (error) { - throw new Error(`Failed to assign Copilot: ${error instanceof Error ? error.message : String(error)}`); - } - } - - /** - * Get private key from environment variables or file - * - * @returns {string} Private key in PEM format with real newlines - * @throws {Error} If GITHUB_PRIVATE_KEY is not set - */ - private getPrivateKey(): string { - if (process.env.GITHUB_PRIVATE_KEY) { - /** - * Get private key from environment variable - * dotenv v16+ handles both multiline strings and escaped \n automatically - * But we check if there are literal \n sequences (not actual newlines) and replace them - */ - let privateKey = process.env.GITHUB_PRIVATE_KEY; - - /** - * Check if the string contains literal \n (backslash followed by n) instead of actual newlines - * This can happen if the value was stored as a single line with escaped newlines - */ - if (privateKey.includes('\\n') && !privateKey.includes('\n')) { - /** - * Replace literal \n with actual newlines - */ - privateKey = privateKey.replace(/\\n/g, '\n'); - } - - return privateKey; - } - - throw new Error('GITHUB_PRIVATE_KEY must be set'); - } - - /** - * Create JWT token for GitHub App authentication - * - * @returns {string} JWT token - */ - private createJWT(): string { - const privateKey = this.getPrivateKey(); - const now = Math.floor(Date.now() / 1000); - - /** - * JWT payload for GitHub App - * - iat: issued at time (current time) - * - exp: expiration time (10 minutes from now, GitHub allows up to 10 minutes) - * - iss: issuer (GitHub App ID) - */ - const payload = { - iat: now - 60, // Allow 1 minute clock skew - exp: now + 600, // 10 minutes expiration - iss: this.appId, - }; - - return jwt.sign(payload, privateKey, { algorithm: 'RS256' }); - } - - /** - * Get installation access token from GitHub API - * - * @param {string} installationId - GitHub App installation ID - * @returns {Promise} Installation access token (valid for 1 hour) - * @throws {Error} If token creation fails - */ - private async createInstallationToken(installationId: string): Promise { - const token = this.createJWT(); - - /** - * Create Octokit instance with JWT authentication - */ - const octokit = new Octokit({ - auth: token, - }); - - try { - /** - * Request installation access token - */ - const { data } = await octokit.rest.apps.createInstallationAccessToken({ - installation_id: parseInt(installationId, 10), - }); - - return data.token; - } catch (error) { - throw new Error(`Failed to create installation token: ${error instanceof Error ? error.message : String(error)}`); - } - } + app.use('/integration/github', githubRouter); } diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts new file mode 100644 index 00000000..32d05981 --- /dev/null +++ b/src/integrations/github/routes.ts @@ -0,0 +1,149 @@ +import express from 'express'; +import { v4 as uuid } from 'uuid'; +import { ObjectId } from 'mongodb'; +import { GitHubService } from './service'; +import { ContextFactories } from '../../types/graphql'; +import { RedisInstallStateStore } from './store/install-state.redis.store'; +import WorkspaceModel from '../../models/workspace'; + +/** + * Create GitHub router + * + * @param factories - context factories for database access + * @returns Express router with GitHub integration endpoints + */ +export function createGitHubRouter(factories: ContextFactories): express.Router { + const router = express.Router(); + const githubService = new GitHubService(); + const stateStore = new RedisInstallStateStore(); + + /** + * GET /integration/github/connect?projectId= + * Initiate GitHub integration connection + */ + router.get('/connect', async (req, res, next) => { + try { + const { projectId } = req.query; + const userId = req.context?.user?.id; + + /** + * Check if user is authenticated + */ + if (!userId) { + res.status(401).json({ error: 'Unauthorized. Please provide authorization token.' }); + + return; + } + + /** + * Validate projectId parameter + */ + if (!projectId || typeof projectId !== 'string') { + res.status(400).json({ error: 'projectId query parameter is required' }); + + return; + } + + /** + * Validate projectId format (MongoDB ObjectId) + */ + if (!ObjectId.isValid(projectId)) { + res.status(400).json({ error: `Invalid projectId format: ${projectId}` }); + + return; + } + + /** + * Find project by ID + */ + const project = await factories.projectsFactory.findById(projectId); + + if (!project) { + res.status(404).json({ error: `Project not found: ${projectId}` }); + + return; + } + + /** + * Check if project is demo project (cannot be modified) + */ + if (project.workspaceId.toString() === '6213b6a01e6281087467cc7a') { + res.status(400).json({ error: 'Unable to update demo project' }); + + return; + } + + /** + * Get workspace to check admin permissions + */ + const workspace = await factories.workspacesFactory.findById(project.workspaceId.toString()); + + if (!workspace) { + res.status(404).json({ error: `Workspace not found: ${project.workspaceId.toString()}` }); + + return; + } + + /** + * Check if user is member of workspace + */ + const member = await workspace.getMemberInfo(userId); + + if (!member || WorkspaceModel.isPendingMember(member)) { + res.status(403).json({ error: 'You are not a member of this workspace' }); + + return; + } + + /** + * Check if user is admin of workspace + */ + if (!member.isAdmin) { + res.status(403).json({ error: 'Not enough permissions. Only workspace admin can connect Task Manager integration.' }); + + return; + } + + /** + * Generate unique state for CSRF protection + * Using UUID v4 for simplicity (alternative: JWT token) + */ + const state = uuid(); + + /** + * Save state data in Redis with TTL + * Data includes: projectId, userId, timestamp + */ + const stateData = { + projectId, + userId, + timestamp: Date.now(), + }; + + await stateStore.saveState(state, stateData); + + console.log( + `[GitHub Integration] Created state for project ${projectId}: ${state.slice(0, 8)}...` + ); + + /** + * Generate GitHub installation URL with state + */ + const installationUrl = githubService.getInstallationUrl(state); + + console.log( + `[GitHub Integration] Redirecting to GitHub installation URL for project ${projectId}` + ); + + /** + * Redirect user to GitHub installation page + */ + res.redirect(installationUrl); + } catch (error) { + console.error('[GitHub Integration] Error in /connect endpoint:', error); + next(error); + } + }); + + return router; +} diff --git a/src/integrations/github/service.ts b/src/integrations/github/service.ts new file mode 100644 index 00000000..9d408aa6 --- /dev/null +++ b/src/integrations/github/service.ts @@ -0,0 +1,360 @@ +import jwt from 'jsonwebtoken'; +import { Octokit } from '@octokit/rest'; +import type { Endpoints } from '@octokit/types'; + +/** + * Type for GitHub Issue creation parameters + * Extracted from Octokit types for POST /repos/{owner}/{repo}/issues + */ +export type IssueData = Pick< + Endpoints['POST /repos/{owner}/{repo}/issues']['parameters'], + 'title' | 'body' | 'labels' +>; + +/** + * Type for GitHub Issue response data + * Extracted from Octokit types for POST /repos/{owner}/{repo}/issues response + */ +export type GitHubIssue = Pick< + Endpoints['POST /repos/{owner}/{repo}/issues']['response']['data'], + 'number' | 'html_url' | 'title' | 'state' +>; + +/** + * Type for GitHub Installation response data + * Simplified version of Octokit Installation type with essential fields only + * account.login and account.type are extracted from the full GitHub account object + */ +export type Installation = { + /** + * Installation ID + */ + id: number; + + /** + * Account (user or organization) that installed the app + */ + account: { + login: string; + type: string; + }; + + /** + * Installation target type + */ + target_type: string; + + /** + * Permissions granted to the app + */ + permissions: Record; +}; + +/** + * Service for interacting with GitHub API + */ +export class GitHubService { + /** + * GitHub App ID from environment variables + */ + private readonly appId: string; + + /** + * GitHub App slug/name from environment variables + */ + private readonly appSlug: string; + + /** + * Creates an instance of GitHubService + */ + constructor() { + if (!process.env.GITHUB_APP_ID) { + throw new Error('GITHUB_APP_ID environment variable is not set'); + } + + this.appId = process.env.GITHUB_APP_ID; + this.appSlug = process.env.GITHUB_APP_SLUG || 'hawk-tracker'; + } + + /** + * Generate URL for GitHub App installation + * + * @param {string} state - State parameter for CSRF protection and context preservation. + * GitHub will return this value unchanged in the callback URL, + * allowing you to verify that the callback corresponds to the original installation request. + * Typically this is a JWT token or a random string (UUID) that serves as a key + * to retrieve stored context data (projectId, userId, etc.) from Redis or session storage. + * + * Example values: + * - JWT: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9qZWN0SWQiOiI2NzgiLCJ1c2VySWQiOiIxMjMiLCJ0aW1lc3RhbXAiOjE3MDk4NzY1NDB9..." + * - UUID: "550e8400-e29b-41d4-a716-446655440000" + * + * @returns {string} Installation URL with state and redirect_url parameters + */ + public getInstallationUrl(state: string): string { + if (!process.env.API_URL) { + throw new Error('API_URL environment variable must be set to generate installation URL with redirect_url'); + } + + /** + * Form callback URL based on API_URL environment variable + * This allows different callback URLs for different environments (dev, staging, production) + */ + const redirectUrl = `${process.env.API_URL}/integration/github/callback`; + + return `https://github.com/apps/${this.appSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(redirectUrl)}`; + } + + /** + * Get installation information + * + * Installation represents a GitHub App installation in a user's or organization's account. + * When a user or organization installs a GitHub App, GitHub creates an Installation object + * that links the app to the account and grants specific permissions to the app for accessing + * repositories and resources. This installation is required to generate installation access tokens + * that allow the app to make API calls on behalf of the installation. + * + * @param {string} installationId - GitHub App installation ID (unique identifier for the installation) + * @returns {Promise} Installation information containing: + * - id: Installation ID + * - account: User or organization that installed the app (with login and type) + * - target_type: Type of target (User or Organization) + * - permissions: Permissions granted to the app for this installation + * @throws {Error} If request fails + */ + public async getInstallationForRepository(installationId: string): Promise { + const token = this.createJWT(); + + /** + * Create Octokit instance with JWT authentication + */ + const octokit = new Octokit({ + auth: token, + }); + + try { + const { data } = await octokit.rest.apps.getInstallation({ + installation_id: parseInt(installationId, 10), + }); + + /** + * Extract account login and type + * account can be User (has 'login' and 'type') or Organization (has 'slug' but not 'login') + */ + let accountLogin = ''; + let accountType = ''; + + if (data.account) { + /** + * Check if account has 'login' property (User) or 'slug' property (Organization) + */ + if ('login' in data.account) { + accountLogin = data.account.login; + accountType = 'login' in data.account && 'type' in data.account ? data.account.type : 'User'; + } else if ('slug' in data.account) { + /** + * For Organization, use 'slug' as login identifier + */ + accountLogin = data.account.slug; + accountType = 'Organization'; + } + } + + return { + id: data.id, + account: { + login: accountLogin, + type: accountType, + }, + target_type: data.target_type, + permissions: data.permissions || {}, + }; + } catch (error) { + throw new Error(`Failed to get installation: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Create a GitHub issue + * + * @param {string} repoFullName - Repository full name (owner/repo) + * @param {string} installationId - GitHub App installation ID + * @param {IssueData} issueData - Issue data (title, body, labels) + * @returns {Promise} Created issue + * @throws {Error} If issue creation fails + */ + public async createIssue( + repoFullName: string, + installationId: string, + issueData: IssueData + ): Promise { + const [owner, repo] = repoFullName.split('/'); + + if (!owner || !repo) { + throw new Error(`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`); + } + + /** + * Get installation access token + */ + const accessToken = await this.createInstallationToken(installationId); + + /** + * Create Octokit instance with installation access token + */ + const octokit = new Octokit({ + auth: accessToken, + }); + + try { + const { data } = await octokit.rest.issues.create({ + owner, + repo, + title: issueData.title, + body: issueData.body, + labels: issueData.labels, + }); + + return { + number: data.number, + html_url: data.html_url, + title: data.title, + state: data.state, + }; + } catch (error) { + throw new Error(`Failed to create issue: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Assign GitHub Copilot to an issue + * + * @param {string} owner - Repository owner + * @param {string} repo - Repository name + * @param {number} issueNumber - Issue number + * @param {string} installationId - GitHub App installation ID + * @returns {Promise} True if assignment was successful + * @throws {Error} If assignment fails + */ + public async assignCopilot( + owner: string, + repo: string, + issueNumber: number, + installationId: string + ): Promise { + /** + * Get installation access token + */ + const accessToken = await this.createInstallationToken(installationId); + + /** + * Create Octokit instance with installation access token + */ + const octokit = new Octokit({ + auth: accessToken, + }); + + try { + /** + * Assign GitHub Copilot (github-copilot[bot]) as assignee + */ + await octokit.rest.issues.addAssignees({ + owner, + repo, + issue_number: issueNumber, + assignees: ['github-copilot[bot]'], + }); + + return true; + } catch (error) { + throw new Error(`Failed to assign Copilot: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Get private key from environment variables or file + * + * @returns {string} Private key in PEM format with real newlines + * @throws {Error} If GITHUB_PRIVATE_KEY is not set + */ + private getPrivateKey(): string { + if (process.env.GITHUB_PRIVATE_KEY) { + /** + * Get private key from environment variable + * dotenv v16+ handles both multiline strings and escaped \n automatically + * But we check if there are literal \n sequences (not actual newlines) and replace them + */ + let privateKey = process.env.GITHUB_PRIVATE_KEY; + + /** + * Check if the string contains literal \n (backslash followed by n) instead of actual newlines + * This can happen if the value was stored as a single line with escaped newlines + */ + if (privateKey.includes('\\n') && !privateKey.includes('\n')) { + /** + * Replace literal \n with actual newlines + */ + privateKey = privateKey.replace(/\\n/g, '\n'); + } + + return privateKey; + } + + throw new Error('GITHUB_PRIVATE_KEY must be set'); + } + + /** + * Create JWT token for GitHub App authentication + * + * @returns {string} JWT token + */ + private createJWT(): string { + const privateKey = this.getPrivateKey(); + const now = Math.floor(Date.now() / 1000); + + /** + * JWT payload for GitHub App + * - iat: issued at time (current time) + * - exp: expiration time (10 minutes from now, GitHub allows up to 10 minutes) + * - iss: issuer (GitHub App ID) + */ + const payload = { + iat: now - 60, // Allow 1 minute clock skew + exp: now + 600, // 10 minutes expiration + iss: this.appId, + }; + + return jwt.sign(payload, privateKey, { algorithm: 'RS256' }); + } + + /** + * Get installation access token from GitHub API + * + * @param {string} installationId - GitHub App installation ID + * @returns {Promise} Installation access token (valid for 1 hour) + * @throws {Error} If token creation fails + */ + private async createInstallationToken(installationId: string): Promise { + const token = this.createJWT(); + + /** + * Create Octokit instance with JWT authentication + */ + const octokit = new Octokit({ + auth: token, + }); + + try { + /** + * Request installation access token + */ + const { data } = await octokit.rest.apps.createInstallationAccessToken({ + installation_id: parseInt(installationId, 10), + }); + + return data.token; + } catch (error) { + throw new Error(`Failed to create installation token: ${error instanceof Error ? error.message : String(error)}`); + } + } +} diff --git a/src/integrations/github/store/InstallStateStoreInterface.ts b/src/integrations/github/store/InstallStateStoreInterface.ts new file mode 100644 index 00000000..bbed981f --- /dev/null +++ b/src/integrations/github/store/InstallStateStoreInterface.ts @@ -0,0 +1,44 @@ +/** + * Interface for GitHub App installation state store implementations + * + * Defines contract for storing temporary GitHub App installation state: + * - Installation state: maps state ID to projectId, userId, and timestamp + * Used for CSRF protection during GitHub App installation flow + */ +export interface InstallStateStoreInterface { + /** + * Store type identifier + * Used for logging and debugging purposes + * + * @example "redis" or "memory" + */ + readonly type: string; + + /** + * Save installation state data + * + * @param stateId - unique state identifier (usually UUID) + * @param data - installation state data (projectId, userId, timestamp) + */ + saveState(stateId: string, data: { projectId: string; userId: string; timestamp: number }): Promise; + + /** + * Get and consume installation state data + * + * @param stateId - state identifier + * @returns installation state data or null if not found/expired + */ + getState(stateId: string): Promise<{ projectId: string; userId: string; timestamp: number } | null>; + + /** + * Stop cleanup timer (for testing) + * Optional method - only needed for in-memory store + */ + stopCleanupTimer?(): void; + + /** + * Clear all stored state (for testing) + * Optional method - only needed for in-memory store + */ + clear?(): void; +} diff --git a/src/integrations/github/store/install-state.redis.store.ts b/src/integrations/github/store/install-state.redis.store.ts new file mode 100644 index 00000000..5c0fd823 --- /dev/null +++ b/src/integrations/github/store/install-state.redis.store.ts @@ -0,0 +1,120 @@ +import { RedisClientType } from 'redis'; +import RedisHelper from '../../../redisHelper'; +import { InstallStateStoreInterface } from './InstallStateStoreInterface'; + +/** + * Redis-based store for GitHub App installation state + * + * Stores temporary data needed for GitHub App installation flow in Redis: + * - Installation state: maps state ID to projectId, userId, and timestamp + * + * This implementation is suitable for multi-instance deployments as it uses + * Redis as the shared state store. TTLs are handled by Redis automatically. + */ +export class RedisInstallStateStore implements InstallStateStoreInterface { + /** + * Store type identifier + */ + public readonly type = 'redis'; + + /** + * Redis helper instance + */ + private redisHelper: RedisHelper; + + /** + * Time-to-live for stored state in seconds (15 minutes) + */ + private readonly TTL_SECONDS = 15 * 60; + + /** + * Prefix for installation state keys in Redis + */ + private readonly STATE_PREFIX = 'github-app-installation:state:'; + + /** + * Store constructor + * + * @param redisHelper - Redis helper instance (defaults to singleton) + */ + constructor(redisHelper?: RedisHelper) { + this.redisHelper = redisHelper || RedisHelper.getInstance(); + } + + /** + * Save installation state data + * + * @param stateId - unique state identifier (usually UUID) + * @param data - installation state data (projectId, userId, timestamp) + */ + public async saveState( + stateId: string, + data: { projectId: string; userId: string; timestamp: number } + ): Promise { + const client = this.getClient(); + const key = `${this.STATE_PREFIX}${stateId}`; + const value = JSON.stringify(data); + + await client.setEx(key, this.TTL_SECONDS, value); + } + + /** + * Get and consume installation state data + * + * @param stateId - state identifier + * @returns installation state data or null if not found/expired + */ + public async getState(stateId: string): Promise<{ projectId: string; userId: string; timestamp: number } | null> { + const client = this.getClient(); + const key = `${this.STATE_PREFIX}${stateId}`; + + /** + * Get and delete atomically to prevent race conditions + * This ensures the state can only be consumed once + * Using MULTI/EXEC for atomic operation (compatible with Redis 5.0+) + */ + const results = await client + .multi() + .get(key) + .del(key) + .exec(); + + if (!results || results.length < 2) { + return null; + } + + const value = results[0] as string | null; + + if (!value) { + return null; + } + + try { + return JSON.parse(value) as { projectId: string; userId: string; timestamp: number }; + } catch (error) { + console.error('[Redis GitHub Install Store] Failed to parse state:', error); + + return null; + } + } + + /** + * Get Redis client + * + * @returns Redis client instance + * @throws Error if Redis client is not available + */ + private getClient(): RedisClientType { + const client = this.redisHelper.getClient(); + + if (!client) { + throw new Error('Redis client is not available. Make sure Redis is initialized.'); + } + + if (!client.isOpen) { + throw new Error('Redis client is not connected. Make sure Redis connection is established.'); + } + + return client; + } +} diff --git a/test/integrations/github.test.ts b/test/integrations/github.test.ts index 32455a46..e7829ec2 100644 --- a/test/integrations/github.test.ts +++ b/test/integrations/github.test.ts @@ -13,7 +13,7 @@ jest.mock('@octokit/rest', () => ({ */ jest.mock('jsonwebtoken'); -import { GitHubService } from '../../src/integrations/github/index'; +import { GitHubService } from '../../src/integrations/github/service'; import jwt from 'jsonwebtoken'; describe('GitHubService', () => { From 49b94a90a0e8420693605bff36e435c9cadfeeb0 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Mon, 19 Jan 2026 00:18:01 +0300 Subject: [PATCH 04/15] Refactor GitHub integration connect endpoint and update dependencies Refactored the /integration/github/connect endpoint to return a JSON object with the installation redirect URL instead of performing a direct redirect, and added colorized logging with environment-based suppression for tests. Updated Jest and argon2 dependencies, added Jest moduleNameMapper for node:crypto and node:util, and introduced mocks for these modules. Added comprehensive tests for the GitHub integration connect route. --- .env.sample | 2 +- jest.config.js | 10 + package.json | 8 +- src/integrations/github/routes.ts | 53 +++- test/__mocks__/node_crypto.js | 5 + test/__mocks__/node_util.js | 5 + test/integrations/github-routes.test.ts | 386 ++++++++++++++++++++++++ yarn.lock | 214 ++++--------- 8 files changed, 506 insertions(+), 177 deletions(-) create mode 100644 test/__mocks__/node_crypto.js create mode 100644 test/__mocks__/node_util.js create mode 100644 test/integrations/github-routes.test.ts diff --git a/.env.sample b/.env.sample index ca88ee6f..785e78c7 100644 --- a/.env.sample +++ b/.env.sample @@ -101,7 +101,7 @@ GITHUB_WEBHOOK_SECRET=623f6ed30b1f762803149893263a95cc2687fe3ce5a9f30648dcbf2571 GITHUB_APP_ID=1234567 # GitHub App slug/name. Used to generate installation URLs -GITHUB_APP_SLUG=hawk-tracker-app; +GITHUB_APP_SLUG=hawk-tracker-app # Private key generated in GitHub app settings GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- diff --git a/jest.config.js b/jest.config.js index 1e7f792d..4c2cc568 100644 --- a/jest.config.js +++ b/jest.config.js @@ -29,6 +29,16 @@ module.exports = { }], }, + /** + * Map node: prefixed imports to mock files + * Jest 27 supports node: prefix for ESM imports, but CommonJS require('node:crypto') + * in modules like argon2.cjs still needs explicit mapping to mocks + */ + moduleNameMapper: { + '^node:crypto$': '/test/__mocks__/node_crypto.js', + '^node:util$': '/test/__mocks__/node_util.js', + }, + /** * Ignore folders */ diff --git a/package.json b/package.json index 77bf4fac..1173eaaa 100644 --- a/package.json +++ b/package.json @@ -22,16 +22,16 @@ "devDependencies": { "@shelf/jest-mongodb": "^6.0.2", "@swc/core": "^1.3.0", - "@types/jest": "^26.0.8", + "@types/jest": "^27.5.0", "@types/xml2js": "^0.4.14", "eslint": "^6.7.2", "eslint-config-codex": "1.2.4", "eslint-plugin-import": "^2.19.1", - "jest": "^26.2.2", + "jest": "^27.6.0", "mongodb-memory-server": "^6.6.1", "nodemon": "^2.0.2", "redis-mock": "^0.56.3", - "ts-jest": "^26.1.4", + "ts-jest": "^27.1.0", "ts-node": "^10.9.1", "typescript": "^4.7.4", "xml2js": "^0.6.2" @@ -64,7 +64,7 @@ "amqp-connection-manager": "^3.1.0", "amqplib": "^0.5.5", "apollo-server-express": "^3.10.0", - "argon2": "^0.28.7", + "argon2": "^0.44.0", "aws-sdk": "^2.1174.0", "axios": "^0.27.2", "body-parser": "^1.19.0", diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index 32d05981..b89ecf94 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -5,6 +5,7 @@ import { GitHubService } from './service'; import { ContextFactories } from '../../types/graphql'; import { RedisInstallStateStore } from './store/install-state.redis.store'; import WorkspaceModel from '../../models/workspace'; +import { sgr, Effect } from '../../utils/ansi'; /** * Create GitHub router @@ -17,6 +18,40 @@ export function createGitHubRouter(factories: ContextFactories): express.Router const githubService = new GitHubService(); const stateStore = new RedisInstallStateStore(); + /** + * Log message with GitHub Integration prefix + * + * @param level - log level ('log', 'warn', 'error', 'info') + * @param args - arguments to log + */ + function log(level: 'log' | 'warn' | 'error' | 'info', ...args: unknown[]): void { + /** + * Disable logging in test environment + */ + if (process.env.NODE_ENV === 'test') { + return; + } + + const colors = { + log: Effect.ForegroundGreen, + warn: Effect.ForegroundYellow, + error: Effect.ForegroundRed, + info: Effect.ForegroundBlue, + }; + + let logger: typeof console.log; + + if (level === 'error') { + logger = console.error; + } else if (level === 'warn') { + logger = console.warn; + } else { + logger = console.log; + } + + logger(sgr('[GitHub Integration]', colors[level]), ...args); + } + /** * GET /integration/github/connect?projectId= * Initiate GitHub integration connection @@ -122,25 +157,25 @@ export function createGitHubRouter(factories: ContextFactories): express.Router await stateStore.saveState(state, stateData); - console.log( - `[GitHub Integration] Created state for project ${projectId}: ${state.slice(0, 8)}...` - ); + log('info', `Created state for project ${sgr(projectId, Effect.ForegroundCyan)}: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); /** * Generate GitHub installation URL with state */ const installationUrl = githubService.getInstallationUrl(state); - console.log( - `[GitHub Integration] Redirecting to GitHub installation URL for project ${projectId}` - ); + log('info', `Generated GitHub installation URL for project ${sgr(projectId, Effect.ForegroundCyan)}`); /** - * Redirect user to GitHub installation page + * Return installation URL in JSON response + * Frontend will handle the redirect using window.location.href + * This allows Authorization header to be sent correctly */ - res.redirect(installationUrl); + res.json({ + redirectUrl: installationUrl, + }); } catch (error) { - console.error('[GitHub Integration] Error in /connect endpoint:', error); + log('error', 'Error in /connect endpoint:', error); next(error); } }); diff --git a/test/__mocks__/node_crypto.js b/test/__mocks__/node_crypto.js new file mode 100644 index 00000000..17f71bf3 --- /dev/null +++ b/test/__mocks__/node_crypto.js @@ -0,0 +1,5 @@ +/** + * Mock for node:crypto to support Jest with CommonJS require() calls + * This re-exports crypto module as node:crypto + */ +module.exports = require('crypto'); diff --git a/test/__mocks__/node_util.js b/test/__mocks__/node_util.js new file mode 100644 index 00000000..f2d4d728 --- /dev/null +++ b/test/__mocks__/node_util.js @@ -0,0 +1,5 @@ +/** + * Mock for node:util to support Jest with CommonJS require() calls + * This re-exports util module as node:util + */ +module.exports = require('util'); diff --git a/test/integrations/github-routes.test.ts b/test/integrations/github-routes.test.ts new file mode 100644 index 00000000..a72ce16f --- /dev/null +++ b/test/integrations/github-routes.test.ts @@ -0,0 +1,386 @@ +import '../../src/env-test'; +import { ObjectId } from 'mongodb'; +import express from 'express'; +import type { Request, Response } from 'express'; +import { createGitHubRouter } from '../../src/integrations/github/routes'; +import { ContextFactories } from '../../src/types/graphql'; +import WorkspaceModel from '../../src/models/workspace'; + +/** + * Mock GitHubService + */ +jest.mock('../../src/integrations/github/service', () => ({ + GitHubService: jest.fn().mockImplementation(() => ({ + getInstallationUrl: jest.fn((state: string) => { + return `https://github.com/apps/test-app/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent('http://localhost:4000/integration/github/callback')}`; + }), + })), +})); + +/** + * Mock install state store + */ +jest.mock('../../src/integrations/github/store/install-state.redis.store', () => ({ + RedisInstallStateStore: jest.fn().mockImplementation(() => ({ + saveState: jest.fn().mockResolvedValue(undefined), + })), +})); + +const DEMO_WORKSPACE_ID = '6213b6a01e6281087467cc7a'; + +function createMockProject(options: { + projectId?: string; + workspaceId?: string; +}): any { + const { + projectId = new ObjectId().toString(), + workspaceId = new ObjectId().toString(), + } = options; + + return { + _id: new ObjectId(projectId), + workspaceId: new ObjectId(workspaceId), + name: 'Test Project', + }; +} + +function createMockWorkspace(options: { + workspaceId?: string; + isAdmin?: boolean; + member?: any; +}): any { + const { + workspaceId = new ObjectId().toString(), + isAdmin = true, + member, + } = options; + + const defaultMember = { + userId: new ObjectId('507f1f77bcf86cd799439011'), + isAdmin, + }; + + return { + _id: new ObjectId(workspaceId), + getMemberInfo: jest.fn().mockResolvedValue(member !== undefined ? member : defaultMember), + }; +} + +/** + * Helper function to make a request to Express app + */ +function makeRequest( + app: express.Application, + method: string, + path: string, + query?: Record +): Promise<{ status: number; body: any }> { + return new Promise((resolve, reject) => { + const url = query ? `${path}?${new URLSearchParams(query).toString()}` : path; + const req = { + method, + url, + originalUrl: url, + path, + query: query || {}, + headers: {}, + get: jest.fn(), + params: {}, + body: {}, + } as any; + + let statusCode = 200; + let jsonCalled = false; + const res = { + status: (code: number) => { + statusCode = code; + + return res; + }, + json: (data: any) => { + jsonCalled = true; + resolve({ status: statusCode, body: data }); + }, + setHeader: jest.fn(), + getHeader: jest.fn(), + end: jest.fn(), + send: jest.fn((data?: any) => { + if (!jsonCalled) { + resolve({ status: statusCode, body: data }); + } + }), + redirect: jest.fn(), + } as any; + + /** + * Use (app as any).handle() as handle method exists but is not in TypeScript types + * This simulates how Express processes requests internally + */ + (app as any).handle(req, res, (err: any) => { + if (err) { + reject(err); + } else if (!jsonCalled) { + /** + * If json was not called, check if response was sent another way + * Wait a bit to allow async handlers to complete + */ + setTimeout(() => { + resolve({ status: statusCode, body: null }); + }, 50); + } + }); + }); +} + +describe('GitHub Routes - /integration/github/connect', () => { + let app: express.Application; + const userId = '507f1f77bcf86cd799439011'; + + /** + * Setup router with factories for testing + */ + function setupRouter(factories: ContextFactories, contextOverrides?: (req: any) => void): void { + /** + * Create new Express app for each test + */ + app = express(); + + /** + * Add middleware to set context (simulating req.context from index.ts) + */ + app.use((req, res, next) => { + req.context = { + user: { + id: userId, + accessTokenExpired: false, + }, + factories, + }; + + /** + * Apply context overrides if provided + */ + if (contextOverrides) { + contextOverrides(req); + } + + next(); + }); + + /** + * Create router with factories and mount it + */ + const router = createGitHubRouter(factories); + app.use('/integration/github', router); + } + + beforeEach(() => { + jest.clearAllMocks(); + + /** + * Setup environment variables + */ + process.env.GITHUB_APP_ID = '123456'; + process.env.API_URL = 'http://localhost:4000'; + }); + + afterEach(() => { + Reflect.deleteProperty(process.env, 'GITHUB_APP_ID'); + Reflect.deleteProperty(process.env, 'API_URL'); + }); + + describe('GET /integration/github/connect', () => { + it('should return JSON with redirectUrl when user is authenticated and is admin', async () => { + const projectId = new ObjectId().toString(); + const workspaceId = new ObjectId().toString(); + const mockProject = createMockProject({ projectId, workspaceId }); + const mockWorkspace = createMockWorkspace({ workspaceId, isAdmin: true }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: { + findById: jest.fn().mockResolvedValue(mockWorkspace), + } as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId }); + + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('redirectUrl'); + expect(response.body.redirectUrl).toContain('https://github.com/apps/test-app/installations/new'); + expect(response.body.redirectUrl).toMatch(/state=[^&]+/); + }); + + it('should return 401 when user is not authenticated', async () => { + const projectId = new ObjectId().toString(); + const factories: ContextFactories = { + projectsFactory: {} as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories, (req) => { + req.context.user.id = undefined; + }); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId }); + + expect(response.status).toBe(401); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('Unauthorized'); + }); + + it('should return 400 when projectId is missing', async () => { + const factories: ContextFactories = { + projectsFactory: {} as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect'); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('projectId'); + }); + + it('should return 400 when projectId format is invalid', async () => { + const factories: ContextFactories = { + projectsFactory: {} as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId: 'invalid-id' }); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('Invalid projectId format'); + }); + + it('should return 404 when project is not found', async () => { + const projectId = new ObjectId().toString(); + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(null), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId }); + + expect(response.status).toBe(404); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('Project not found'); + }); + + it('should return 400 when project is demo project', async () => { + const projectId = new ObjectId().toString(); + const mockProject = createMockProject({ + projectId, + workspaceId: DEMO_WORKSPACE_ID, + }); + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId }); + + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('Unable to update demo project'); + }); + + it('should return 403 when user is not admin', async () => { + const projectId = new ObjectId().toString(); + const workspaceId = new ObjectId().toString(); + const mockProject = createMockProject({ projectId, workspaceId }); + const mockWorkspace = createMockWorkspace({ workspaceId, isAdmin: false }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: { + findById: jest.fn().mockResolvedValue(mockWorkspace), + } as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId }); + + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('Not enough permissions'); + }); + + it('should return 403 when user is not a member of workspace', async () => { + const projectId = new ObjectId().toString(); + const workspaceId = new ObjectId().toString(); + const mockProject = createMockProject({ projectId, workspaceId }); + const mockWorkspace = createMockWorkspace({ workspaceId, member: null }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: { + findById: jest.fn().mockResolvedValue(mockWorkspace), + } as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/connect', { projectId }); + + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toContain('not a member'); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 8e3c65a4..e4ef4f98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -440,6 +440,11 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@epic-web/invariant@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" + integrity sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA== + "@graphql-tools/merge@8.3.1", "@graphql-tools/merge@^8.3.1": version "8.3.1" resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-8.3.1.tgz#06121942ad28982a14635dbc87b5d488a041d722" @@ -773,21 +778,6 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@mapbox/node-pre-gyp@^1.0.9": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz#09a8781a3a036151cdebbe8719d6f8b25d4058bc" - integrity sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw== - dependencies: - detect-libc "^2.0.0" - https-proxy-agent "^5.0.0" - make-dir "^3.1.0" - node-fetch "^2.6.7" - nopt "^5.0.0" - npmlog "^5.0.1" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.11" - "@mongodb-js/saslprep@^1.3.0": version "1.4.4" resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz#34a946ff6ae142e8f2259b87f2935f8284ba874d" @@ -1928,32 +1918,20 @@ apollo-server-types@^3.6.2: apollo-reporting-protobuf "^3.3.2" apollo-server-env "^4.2.1" -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - -are-we-there-yet@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" - integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argon2@^0.28.7: - version "0.28.7" - resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.28.7.tgz#f6cc1f1c9e3ef5455d2186d4ec5b27cfeda41591" - integrity sha512-pvsScM3Fq7b+jolXkZHh8nRQx0uD/WeelnwYPMRpn4pAydoa1gqeL/KRdWAag4Hnu1TJNBTAfqyTjV+ZHwNnYA== +argon2@^0.44.0: + version "0.44.0" + resolved "https://registry.yarnpkg.com/argon2/-/argon2-0.44.0.tgz#65a5ba662bba66af41407aa0457decf4af101742" + integrity sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig== dependencies: - "@mapbox/node-pre-gyp" "^1.0.9" "@phc/format" "^1.0.0" - node-addon-api "^5.0.0" + cross-env "^10.0.0" + node-addon-api "^8.5.0" + node-gyp-build "^4.8.4" argparse@^1.0.7: version "1.0.10" @@ -2484,11 +2462,6 @@ chokidar@^3.5.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -2608,11 +2581,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -2650,11 +2618,6 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -console-control-strings@^1.0.0, console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -2712,6 +2675,14 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cross-env@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-10.1.0.tgz#cfd2a6200df9ed75bfb9cb3d7ce609c13ea21783" + integrity sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw== + dependencies: + "@epic-web/invariant" "^1.0.0" + cross-spawn "^7.0.6" + cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -2732,6 +2703,15 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + cssfilter@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" @@ -2875,11 +2855,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== - denque@^1.4.1: version "1.5.1" resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" @@ -2900,11 +2875,6 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-libc@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3709,13 +3679,6 @@ fs-extra@9.0.1: jsonfile "^6.0.1" universalify "^1.0.0" -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3756,21 +3719,6 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -gauge@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" - integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.2" - console-control-strings "^1.0.0" - has-unicode "^2.0.1" - object-assign "^4.1.1" - signal-exit "^3.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.2" - generic-pool@3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4" @@ -3975,11 +3923,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -5212,7 +5155,7 @@ lru-cache@^7.10.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.13.1.tgz#267a81fbd0881327c46a81c5922606a2cfe336c4" integrity sha512-CHqbAq7NFlW3RSnoWXLJBxCWaZVBrfa9UEHId2M3AW8iEBurbqduNexEUCGc3SHc6iCYXNJCDi903LajSVAEPQ== -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: +make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -5353,21 +5296,6 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -minipass@^3.0.0: - version "3.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" - integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== - dependencies: - yallist "^4.0.0" - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -5376,7 +5304,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@1.x, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -5558,10 +5486,10 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-addon-api@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" - integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== +node-addon-api@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.5.0.tgz#c91b2d7682fa457d2e1c388150f0dff9aafb8f3f" + integrity sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A== node-fetch@^2.6.0: version "2.7.0" @@ -5577,6 +5505,11 @@ node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" +node-gyp-build@^4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -5615,13 +5548,6 @@ nodemon@^2.0.2: touch "^3.1.0" undefsafe "^2.0.5" -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -5665,22 +5591,12 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" -npmlog@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" - integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== - dependencies: - are-we-there-yet "^2.0.0" - console-control-strings "^1.1.0" - gauge "^3.0.0" - set-blocking "^2.0.0" - nwsapi@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== -object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -6179,15 +6095,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -6355,7 +6262,7 @@ rimraf@^2.6.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -6806,15 +6713,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -6824,6 +6722,15 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string.prototype.trimend@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" @@ -6967,18 +6874,6 @@ tar-stream@^3.1.7: fast-fifo "^1.2.0" streamx "^2.15.0" -tar@^6.1.11: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - tdigest@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.2.tgz#96c64bac4ff10746b910b0e23b515794e12faced" @@ -7567,13 +7462,6 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" From cda24b295b7287f1791b9d4da77ddef1cca97322 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Mon, 19 Jan 2026 01:05:22 +0300 Subject: [PATCH 05/15] Add GitHub integration callback and webhook endpoints Implemented /callback endpoint to handle GitHub App installation callbacks and save configuration to the project. Added /webhook endpoint to securely process GitHub webhook events, including removal of taskManager config on installation deletion. Improved logging with project context and added utility functions for URL building and signature verification. --- src/integrations/github/routes.ts | 334 +++++++++++++++++++++++++++++- 1 file changed, 330 insertions(+), 4 deletions(-) diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index b89ecf94..a237dab3 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -1,11 +1,13 @@ import express from 'express'; import { v4 as uuid } from 'uuid'; import { ObjectId } from 'mongodb'; +import { createHmac } from 'crypto'; import { GitHubService } from './service'; import { ContextFactories } from '../../types/graphql'; import { RedisInstallStateStore } from './store/install-state.redis.store'; import WorkspaceModel from '../../models/workspace'; import { sgr, Effect } from '../../utils/ansi'; +import { databases } from '../../mongo'; /** * Create GitHub router @@ -13,18 +15,45 @@ import { sgr, Effect } from '../../utils/ansi'; * @param factories - context factories for database access * @returns Express router with GitHub integration endpoints */ +/** + * Default task threshold for automatic task creation + * Minimum totalCount required to trigger auto-task creation + */ +const DEFAULT_TASK_THRESHOLD_TOTAL_COUNT = 50; + export function createGitHubRouter(factories: ContextFactories): express.Router { const router = express.Router(); const githubService = new GitHubService(); const stateStore = new RedisInstallStateStore(); + /** + * Build redirect URL to Garage frontend + * + * @param path - path on Garage (e.g., '/project/123/settings/task-manager') + * @param params - URL search parameters (e.g., { success: 'true' } or { error: 'message' }) + * @returns Full URL string for redirect + */ + function buildGarageRedirectUrl(path: string, params?: Record): string { + const garageUrl = process.env.GARAGE_URL || 'https://garage.hawk.so'; + const redirectUrl = new URL(path, garageUrl); + + if (params) { + for (const [key, value] of Object.entries(params)) { + redirectUrl.searchParams.set(key, value); + } + } + + return redirectUrl.toString(); + } + /** * Log message with GitHub Integration prefix * * @param level - log level ('log', 'warn', 'error', 'info') + * @param projectId - optional project ID to include in log prefix * @param args - arguments to log */ - function log(level: 'log' | 'warn' | 'error' | 'info', ...args: unknown[]): void { + function log(level: 'log' | 'warn' | 'error' | 'info', projectIdOrFirstArg?: string | unknown, ...args: unknown[]): void { /** * Disable logging in test environment */ @@ -49,7 +78,28 @@ export function createGitHubRouter(factories: ContextFactories): express.Router logger = console.log; } - logger(sgr('[GitHub Integration]', colors[level]), ...args); + /** + * Check if first argument is projectId (string) or regular log argument + * projectId should be a string and valid ObjectId format + */ + let projectId: string | undefined; + let logArgs: unknown[]; + + if (typeof projectIdOrFirstArg === 'string' && ObjectId.isValid(projectIdOrFirstArg)) { + projectId = `pid: ${projectIdOrFirstArg}`; + logArgs = args; + } else { + logArgs = projectIdOrFirstArg !== undefined ? [projectIdOrFirstArg, ...args] : args; + } + + /** + * Build log prefix with optional projectId + */ + const prefix = projectId + ? `${sgr('[GitHub Integration]', colors[level])} ${sgr(`[${projectId}]`, Effect.ForegroundCyan)}` + : sgr('[GitHub Integration]', colors[level]); + + logger(prefix, ...logArgs); } /** @@ -157,14 +207,14 @@ export function createGitHubRouter(factories: ContextFactories): express.Router await stateStore.saveState(state, stateData); - log('info', `Created state for project ${sgr(projectId, Effect.ForegroundCyan)}: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); + log('info', projectId, `Created state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); /** * Generate GitHub installation URL with state */ const installationUrl = githubService.getInstallationUrl(state); - log('info', `Generated GitHub installation URL for project ${sgr(projectId, Effect.ForegroundCyan)}`); + log('info', projectId, 'Generated GitHub installation URL: ' + sgr(installationUrl, Effect.ForegroundGreen)); /** * Return installation URL in JSON response @@ -180,5 +230,281 @@ export function createGitHubRouter(factories: ContextFactories): express.Router } }); + /** + * GET /integration/github/callback?state=&installation_id= + * Handle GitHub App installation callback + */ + router.get('/callback', async (req, res, next) => { + try { + const { state, installation_id } = req.query; + + /** + * Validate required parameters + */ + if (!state || typeof state !== 'string') { + return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + error: 'Missing or invalid state', + })); + } + + if (!installation_id || typeof installation_id !== 'string') { + return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + error: 'Missing or invalid installation_id parameter', + })); + } + + /** + * Verify state (CSRF protection) + * getState() atomically gets and deletes the state, preventing reuse + */ + const stateData = await stateStore.getState(state); + + if (!stateData) { + log('warn', `Invalid or expired state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); + + return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + error: 'Invalid or expired state. Please try connecting again.', + })); + } + + const { projectId, userId } = stateData; + + log('info', projectId, `Processing callback initiated by user ${sgr(userId, Effect.ForegroundCyan)}`); + + /** + * Verify project exists + */ + const project = await factories.projectsFactory.findById(projectId); + + if (!project) { + log('error', projectId, 'Project not found'); + + return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + error: `Project not found: ${projectId}`, + })); + } + + /** + * Get installation info from GitHub + */ + let installation; + + try { + installation = await githubService.getInstallationForRepository(installation_id); + log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`); + } catch (error) { + log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`); + + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + error: 'Failed to retrieve GitHub installation information. Please try again.', + })); + } + + /** + * For now, we save only installationId + * repoId and repoFullName will be set when creating the first issue or can be configured later + * GitHub App installation can include multiple repositories, so we don't know which one to use yet + */ + const taskManagerConfig = { + type: 'github', + autoTaskEnabled: false, + taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT, + assignAgent: false, + connectedAt: new Date(), + updatedAt: new Date(), + config: { + installationId: installation_id, + repoId: '', + repoFullName: '', + }, + }; + + let successRedirectUrl = buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + success: 'true', + }); + + /** + * Save taskManager configuration to project + */ + try { + await project.updateProject(({ + taskManager: taskManagerConfig, + }) as any); + + log('info', projectId, 'Successfully connected GitHub integration. Redirecting to ' + sgr(successRedirectUrl, Effect.ForegroundGreen)); + } catch (error) { + log('error', projectId, `Failed to save taskManager config: ${error instanceof Error ? error.message : String(error)}`); + + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + error: 'Failed to save Task Manager configuration. Please try again.', + })); + } + + /** + * Redirect to Garage with success parameter + */ + return res.redirect(successRedirectUrl); + } catch (error) { + log('error', 'Error in /callback endpoint:', error); + next(error); + } + }); + + /** + * POST /integration/github/webhook + * Handle GitHub App webhook events + */ + router.post('/webhook', express.raw({ type: 'application/json' }), async (req, res, next) => { + try { + /** + * Get webhook secret from environment + */ + const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET; + + if (!webhookSecret) { + log('error', 'GITHUB_WEBHOOK_SECRET is not configured'); + res.status(500).json({ error: 'Webhook secret not configured' }); + + return; + } + + /** + * Get signature from request headers + * GitHub sends signature in X-Hub-Signature-256 header as sha256= + */ + const signature = req.headers['x-hub-signature-256'] as string | undefined; + + if (!signature) { + log('warn', 'Missing X-Hub-Signature-256 header'); + + return res.status(401).json({ error: 'Missing signature header' }); + } + + /** + * Verify webhook signature using HMAC SHA-256 + */ + const payload = req.body as Buffer; + const hmac = createHmac('sha256', webhookSecret); + hmac.update(payload as any); + const calculatedSignature = `sha256=${hmac.digest('hex')}`; + + /** + * Use timing-safe comparison to prevent timing attacks + */ + let signatureValid = false; + + if (signature.length === calculatedSignature.length) { + let match = true; + + for (let i = 0; i < signature.length; i++) { + if (signature[i] !== calculatedSignature[i]) { + match = false; + } + } + + signatureValid = match; + } + + if (!signatureValid) { + log('warn', 'Invalid webhook signature'); + + return res.status(401).json({ error: 'Invalid signature' }); + } + + /** + * Parse webhook payload + */ + let payloadData: any; + + try { + payloadData = JSON.parse(payload.toString()); + } catch (error) { + log('error', 'Failed to parse webhook payload:', error); + + return res.status(400).json({ error: 'Invalid JSON payload' }); + } + + const eventType = req.headers['x-github-event'] as string | undefined; + const installationId = payloadData.installation?.id?.toString(); + + log('info', `Received webhook event: ${sgr(eventType || 'unknown', Effect.ForegroundCyan)}`); + + /** + * Handle installation.deleted event + */ + if (eventType === 'installation' && payloadData.action === 'deleted') { + if (!installationId) { + log('warn', 'installation.deleted event received but installation_id is missing'); + + return res.status(200).json({ message: 'Event received but no installation_id provided' }); + } + + log('info', `Processing installation.deleted for installation_id: ${sgr(installationId, Effect.ForegroundCyan)}`); + + /** + * Find all projects with this installationId + * Using MongoDB query directly as projectsFactory doesn't have a method for this + */ + const projectsCollection = databases.hawk?.collection('projects'); + + if (!projectsCollection) { + log('error', 'MongoDB projects collection is not available'); + + return res.status(500).json({ error: 'Database connection error' }); + } + + try { + const projects = await projectsCollection + .find({ + 'taskManager.config.installationId': installationId, + }) + .toArray(); + + log('info', `Found ${sgr(projects.length.toString(), Effect.ForegroundCyan)} project(s) with installation_id ${installationId}`); + + /** + * Remove taskManager configuration from all projects + */ + if (projects.length > 0) { + const projectIds = projects.map((p) => p._id.toString()); + + await projectsCollection.updateMany( + { + 'taskManager.config.installationId': installationId, + }, + { + $unset: { + taskManager: '', + }, + $set: { + updatedAt: new Date(), + }, + } + ); + + log('info', `Removed taskManager configuration from ${sgr(projects.length.toString(), Effect.ForegroundCyan)} project(s): ${projectIds.join(', ')}`); + } + } catch (error) { + log('error', `Failed to remove taskManager configurations: ${error instanceof Error ? error.message : String(error)}`); + + return res.status(500).json({ error: 'Failed to process installation.deleted event' }); + } + } else { + /** + * Log other events for monitoring + */ + log('info', `Unhandled webhook event: ${sgr(eventType || 'unknown', Effect.ForegroundGray)} (action: ${sgr(payloadData.action || 'unknown', Effect.ForegroundGray)})`); + } + + /** + * Return 200 OK for successful processing + */ + res.status(200).json({ message: 'Webhook processed successfully' }); + } catch (error) { + log('error', 'Error in /webhook endpoint:', error); + next(error); + } + }); + return router; } From 96ad7b15b282b9a5c4a7bc5a948536b5dcedd9ff Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Fri, 23 Jan 2026 21:55:17 +0300 Subject: [PATCH 06/15] Add GitHub repository selection endpoints and types Introduces endpoints to list and update GitHub repositories for a project, adds a Repository type to the GitHub service, and refactors project admin access validation. Also adds the TaskManagerItem GraphQL type and links it to the Event type for improved integration with task managers like GitHub Issues. --- src/integrations/github/index.ts | 2 +- src/integrations/github/routes.ts | 342 +++++++++++++++++++++------ src/integrations/github/service.ts | 177 ++++++++++++-- src/typeDefs/event.ts | 11 +- src/typeDefs/eventTaskManagerItem.ts | 43 ++++ src/typeDefs/index.ts | 2 + 6 files changed, 482 insertions(+), 95 deletions(-) create mode 100644 src/typeDefs/eventTaskManagerItem.ts diff --git a/src/integrations/github/index.ts b/src/integrations/github/index.ts index b2fedfa2..00942422 100644 --- a/src/integrations/github/index.ts +++ b/src/integrations/github/index.ts @@ -5,7 +5,7 @@ import { ContextFactories } from '../../types/graphql'; /** * Re-export types and service from service.ts for backward compatibility */ -export { GitHubService, IssueData, GitHubIssue, Installation } from './service'; +export { GitHubService, IssueData, GitHubIssue, Installation, Repository } from './service'; /** * Append GitHub routes to Express app diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index a237dab3..8331c11b 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -1,3 +1,4 @@ +import '../../typeDefs/expressContext'; import express from 'express'; import { v4 as uuid } from 'uuid'; import { ObjectId } from 'mongodb'; @@ -46,6 +47,105 @@ export function createGitHubRouter(factories: ContextFactories): express.Router return redirectUrl.toString(); } + /** + * Validate project access and admin permissions + * Performs common checks: authentication, projectId validation, project existence, workspace membership, admin rights + * + * @param req - Express request object + * @param res - Express response object + * @param projectId - project ID from query parameters + * @param errorMessagePrefix - prefix for admin permission error message (e.g., "connect Task Manager integration") + * @returns Object with project, workspace, and userId if validation passes, null otherwise (response already sent) + */ + async function validateProjectAdminAccess( + req: express.Request, + res: express.Response, + projectId: string | undefined, + errorMessagePrefix: string = 'perform this action' + ): Promise<{ project: any; workspace: any; userId: string } | null> { + const userId = req.context?.user?.id; + + /** + * Check if user is authenticated + */ + if (!userId) { + res.status(401).json({ error: 'Unauthorized. Please provide authorization token.' }); + + return null; + } + + /** + * Validate projectId parameter + */ + if (!projectId || typeof projectId !== 'string') { + res.status(400).json({ error: 'projectId query parameter is required' }); + + return null; + } + + /** + * Validate projectId format (MongoDB ObjectId) + */ + if (!ObjectId.isValid(projectId)) { + res.status(400).json({ error: `Invalid projectId format: ${projectId}` }); + + return null; + } + + /** + * Find project by ID + */ + const project = await factories.projectsFactory.findById(projectId); + + if (!project) { + res.status(404).json({ error: `Project not found: ${projectId}` }); + + return null; + } + + /** + * Check if project is demo project (cannot be modified) + */ + if (project.workspaceId.toString() === '6213b6a01e6281087467cc7a') { + res.status(400).json({ error: 'Unable to update demo project' }); + + return null; + } + + /** + * Get workspace to check admin permissions + */ + const workspace = await factories.workspacesFactory.findById(project.workspaceId.toString()); + + if (!workspace) { + res.status(404).json({ error: `Workspace not found: ${project.workspaceId.toString()}` }); + + return null; + } + + /** + * Check if user is member of workspace + */ + const member = await workspace.getMemberInfo(userId); + + if (!member || WorkspaceModel.isPendingMember(member)) { + res.status(403).json({ error: 'You are not a member of this workspace' }); + + return null; + } + + /** + * Check if user is admin of workspace + */ + if (!member.isAdmin) { + res.status(403).json({ error: `Not enough permissions. Only workspace admin can ${errorMessagePrefix}.` }); + + return null; + } + + return { project, workspace, userId }; + } + /** * Log message with GitHub Integration prefix * @@ -109,85 +209,18 @@ export function createGitHubRouter(factories: ContextFactories): express.Router router.get('/connect', async (req, res, next) => { try { const { projectId } = req.query; - const userId = req.context?.user?.id; - - /** - * Check if user is authenticated - */ - if (!userId) { - res.status(401).json({ error: 'Unauthorized. Please provide authorization token.' }); - - return; - } - - /** - * Validate projectId parameter - */ - if (!projectId || typeof projectId !== 'string') { - res.status(400).json({ error: 'projectId query parameter is required' }); - - return; - } - - /** - * Validate projectId format (MongoDB ObjectId) - */ - if (!ObjectId.isValid(projectId)) { - res.status(400).json({ error: `Invalid projectId format: ${projectId}` }); - - return; - } - - /** - * Find project by ID - */ - const project = await factories.projectsFactory.findById(projectId); - - if (!project) { - res.status(404).json({ error: `Project not found: ${projectId}` }); - - return; - } /** - * Check if project is demo project (cannot be modified) + * Validate project access and admin permissions */ - if (project.workspaceId.toString() === '6213b6a01e6281087467cc7a') { - res.status(400).json({ error: 'Unable to update demo project' }); + const access = await validateProjectAdminAccess(req, res, projectId as string | undefined, 'connect Task Manager integration'); + if (!access) { return; } - /** - * Get workspace to check admin permissions - */ - const workspace = await factories.workspacesFactory.findById(project.workspaceId.toString()); - - if (!workspace) { - res.status(404).json({ error: `Workspace not found: ${project.workspaceId.toString()}` }); - - return; - } - - /** - * Check if user is member of workspace - */ - const member = await workspace.getMemberInfo(userId); - - if (!member || WorkspaceModel.isPendingMember(member)) { - res.status(403).json({ error: 'You are not a member of this workspace' }); - - return; - } - - /** - * Check if user is admin of workspace - */ - if (!member.isAdmin) { - res.status(403).json({ error: 'Not enough permissions. Only workspace admin can connect Task Manager integration.' }); - - return; - } + const { project, userId } = access; + const validatedProjectId = project._id.toString(); /** * Generate unique state for CSRF protection @@ -200,21 +233,21 @@ export function createGitHubRouter(factories: ContextFactories): express.Router * Data includes: projectId, userId, timestamp */ const stateData = { - projectId, + projectId: validatedProjectId, userId, timestamp: Date.now(), }; await stateStore.saveState(state, stateData); - log('info', projectId, `Created state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); + log('info', validatedProjectId, `Created state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); /** * Generate GitHub installation URL with state */ const installationUrl = githubService.getInstallationUrl(state); - log('info', projectId, 'Generated GitHub installation URL: ' + sgr(installationUrl, Effect.ForegroundGreen)); + log('info', validatedProjectId, 'Generated GitHub installation URL: ' + sgr(installationUrl, Effect.ForegroundGreen)); /** * Return installation URL in JSON response @@ -238,6 +271,11 @@ export function createGitHubRouter(factories: ContextFactories): express.Router try { const { state, installation_id } = req.query; + /** + * Log callback request for debugging + */ + log('info', `Callback received: state=${state}, installation_id=${installation_id}, query=${JSON.stringify(req.query)}`); + /** * Validate required parameters */ @@ -506,5 +544,159 @@ export function createGitHubRouter(factories: ContextFactories): express.Router } }); + /** + * GET /integration/github/repositories?projectId= + * Get list of repositories accessible to GitHub App installation + */ + router.get('/repositories', async (req, res, next) => { + try { + const { projectId } = req.query; + + /** + * Validate project access and admin permissions + */ + const access = await validateProjectAdminAccess(req, res, projectId as string | undefined, 'access repository list'); + + if (!access) { + return; + } + + const { project } = access; + + /** + * Check if taskManager is configured + */ + const taskManager = (project as any).taskManager; + + if (!taskManager) { + res.status(400).json({ error: 'Task Manager is not configured for this project' }); + + return; + } + + /** + * Extract installationId from project configuration + */ + const installationId = taskManager.config.installationId; + + if (!installationId) { + res.status(400).json({ error: 'GitHub installation ID is not configured for this project' }); + + return; + } + + /** + * Get list of repositories from GitHub + */ + try { + const repositories = await githubService.getRepositoriesForInstallation(installationId); + + /** + * Log repository details for debugging + */ + const repoOwners = [...new Set(repositories.map((r) => r.fullName.split('/')[0]))]; + log('info', projectId, `Retrieved ${repositories.length} repository(ies) for installation ${installationId}`); + log('info', projectId, `Repository owners: ${repoOwners.join(', ')}`); + + res.json({ + repositories, + }); + } catch (error) { + log('error', projectId, `Failed to get repositories: ${error instanceof Error ? error.message : String(error)}`); + + res.status(500).json({ + error: 'Failed to retrieve repositories from GitHub. Please try again.', + }); + } + } catch (error) { + log('error', 'Error in /repositories endpoint:', error); + next(error); + } + }); + + /** + * PUT /integration/github/repository?projectId= + * Update selected repository for GitHub App installation + */ + router.put('/repository', async (req, res, next) => { + try { + const { projectId } = req.query; + const { repoId, repoFullName } = req.body; + + /** + * Validate project access and admin permissions + */ + const access = await validateProjectAdminAccess(req, res, projectId as string | undefined, 'update repository selection'); + + if (!access) { + return; + } + + const { project } = access; + const validatedProjectId = project._id.toString(); + + /** + * Validate request body + */ + if (!repoId || typeof repoId !== 'string') { + res.status(400).json({ error: 'repoId is required and must be a string' }); + + return; + } + + if (!repoFullName || typeof repoFullName !== 'string') { + res.status(400).json({ error: 'repoFullName is required and must be a string' }); + + return; + } + + /** + * Check if taskManager is configured + */ + const taskManager = (project as any).taskManager; + + if (!taskManager) { + res.status(400).json({ error: 'Task Manager is not configured for this project' }); + + return; + } + + /** + * Update taskManager config with selected repository + */ + const updatedTaskManager = { + ...taskManager, + config: { + ...taskManager.config, + repoId, + repoFullName, + }, + updatedAt: new Date(), + }; + + try { + await project.updateProject({ + taskManager: updatedTaskManager, + } as any); + + log('info', validatedProjectId, `Updated repository selection: ${repoFullName} (${repoId})`); + + res.json({ + success: true, + message: 'Repository selection updated successfully', + }); + } catch (error) { + log('error', validatedProjectId, `Failed to update repository selection: ${error instanceof Error ? error.message : String(error)}`); + + res.status(500).json({ + error: 'Failed to update repository selection. Please try again.', + }); + } + } catch (error) { + log('error', 'Error in /repository endpoint:', error); + next(error); + } + }); + return router; } diff --git a/src/integrations/github/service.ts b/src/integrations/github/service.ts index 9d408aa6..efe739d5 100644 --- a/src/integrations/github/service.ts +++ b/src/integrations/github/service.ts @@ -20,6 +20,47 @@ export type GitHubIssue = Pick< 'number' | 'html_url' | 'title' | 'state' >; +/** + * Type for GitHub Repository data + * Ephemeral data, not stored in database + */ +export type Repository = { + /** + * Repository ID + */ + id: string; + + /** + * Repository name (without owner) + */ + name: string; + + /** + * Repository full name (owner/repo) + */ + fullName: string; + + /** + * Whether repository is private + */ + private: boolean; + + /** + * Repository HTML URL + */ + htmlUrl: string; + + /** + * Last update date + */ + updatedAt: Date; + + /** + * Primary programming language + */ + language: string | null; +}; + /** * Type for GitHub Installation response data * Simplified version of Octokit Installation type with essential fields only @@ -64,6 +105,12 @@ export class GitHubService { */ private readonly appSlug: string; + /** + * Default timeout for GitHub API requests (in milliseconds) + * Increased from default 10s to 60s to handle slow network connections + */ + private static readonly DEFAULT_TIMEOUT = 60000; + /** * Creates an instance of GitHubService */ @@ -76,6 +123,21 @@ export class GitHubService { this.appSlug = process.env.GITHUB_APP_SLUG || 'hawk-tracker'; } + /** + * Create Octokit instance with configured timeout + * + * @param auth - Authentication token (JWT or installation access token) + * @returns Configured Octokit instance + */ + private createOctokit(auth: string): Octokit { + return new Octokit({ + auth, + request: { + timeout: GitHubService.DEFAULT_TIMEOUT, + }, + }); + } + /** * Generate URL for GitHub App installation * @@ -90,6 +152,10 @@ export class GitHubService { * - UUID: "550e8400-e29b-41d4-a716-446655440000" * * @returns {string} Installation URL with state and redirect_url parameters + * + * Note: Both Setup URL (in GitHub App settings) and redirect_url parameter can be used. + * The redirect_url parameter takes precedence if provided. We use redirect_url to ensure + * the state parameter is properly passed to our callback endpoint. */ public getInstallationUrl(state: string): string { if (!process.env.API_URL) { @@ -99,9 +165,15 @@ export class GitHubService { /** * Form callback URL based on API_URL environment variable * This allows different callback URLs for different environments (dev, staging, production) + * The redirect_url parameter ensures GitHub redirects to our callback with state preserved */ const redirectUrl = `${process.env.API_URL}/integration/github/callback`; + /** + * Include both state and redirect_url parameters + * The redirect_url parameter ensures GitHub redirects to our callback endpoint + * even if Setup URL is configured differently in GitHub App settings + */ return `https://github.com/apps/${this.appSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(redirectUrl)}`; } @@ -126,11 +198,9 @@ export class GitHubService { const token = this.createJWT(); /** - * Create Octokit instance with JWT authentication + * Create Octokit instance with JWT authentication and configured timeout */ - const octokit = new Octokit({ - auth: token, - }); + const octokit = this.createOctokit(token); try { const { data } = await octokit.rest.apps.getInstallation({ @@ -174,6 +244,87 @@ export class GitHubService { } } + /** + * Get list of repositories accessible to GitHub App installation + * + * @param {string} installationId - GitHub App installation ID + * @returns {Promise} Array of repositories accessible to the installation + * @throws {Error} If request fails + */ + public async getRepositoriesForInstallation(installationId: string): Promise { + /** + * Get installation access token + */ + const accessToken = await this.createInstallationToken(installationId); + + /** + * Create Octokit instance with installation access token and configured timeout + */ + const octokit = this.createOctokit(accessToken); + + try { + /** + * Get installation info using JWT token (not installation access token) + * This is needed to check what account/organization it's installed on + */ + const jwtToken = this.createJWT(); + const jwtOctokit = this.createOctokit(jwtToken); + + const installationInfo = await jwtOctokit.rest.apps.getInstallation({ + installation_id: parseInt(installationId, 10), + }); + + /** + * Log installation info for debugging + */ + console.log('Installation info:', { + id: installationInfo.data.id, + account: installationInfo.data.account, + target_type: installationInfo.data.target_type, + repository_selection: installationInfo.data.repository_selection, + }); + + /** + * Get all repositories accessible to the installation + * Use Octokit's paginate helper to automatically fetch all pages + * This ensures we get repositories from both personal accounts and organizations + * Use installation access token for this call + */ + const repositoriesData = await octokit.paginate( + octokit.rest.apps.listReposAccessibleToInstallation, + { + installation_id: parseInt(installationId, 10), + per_page: 100, + } + ); + + console.log(`Total repositories fetched: ${repositoriesData.length}`); + + /** + * Transform GitHub repository objects to our Repository type + * Sort by updatedAt descending (newest first) + */ + const repositories = repositoriesData.map((repo) => ({ + id: repo.id.toString(), + name: repo.name, + fullName: repo.full_name, + private: repo.private || false, + htmlUrl: repo.html_url, + updatedAt: repo.updated_at ? new Date(repo.updated_at) : new Date(0), + language: repo.language || null, + })); + + /** + * Sort repositories by updatedAt descending (newest first) + */ + return repositories.sort((a, b) => { + return b.updatedAt.getTime() - a.updatedAt.getTime(); + }); + } catch (error) { + throw new Error(`Failed to get repositories: ${error instanceof Error ? error.message : String(error)}`); + } + } + /** * Create a GitHub issue * @@ -200,11 +351,9 @@ export class GitHubService { const accessToken = await this.createInstallationToken(installationId); /** - * Create Octokit instance with installation access token + * Create Octokit instance with installation access token and configured timeout */ - const octokit = new Octokit({ - auth: accessToken, - }); + const octokit = this.createOctokit(accessToken); try { const { data } = await octokit.rest.issues.create({ @@ -248,11 +397,9 @@ export class GitHubService { const accessToken = await this.createInstallationToken(installationId); /** - * Create Octokit instance with installation access token + * Create Octokit instance with installation access token and configured timeout */ - const octokit = new Octokit({ - auth: accessToken, - }); + const octokit = this.createOctokit(accessToken); try { /** @@ -338,11 +485,9 @@ export class GitHubService { const token = this.createJWT(); /** - * Create Octokit instance with JWT authentication + * Create Octokit instance with JWT authentication and configured timeout */ - const octokit = new Octokit({ - auth: token, - }); + const octokit = this.createOctokit(token); try { /** diff --git a/src/typeDefs/event.ts b/src/typeDefs/event.ts index a82b6762..c2069419 100644 --- a/src/typeDefs/event.ts +++ b/src/typeDefs/event.ts @@ -282,6 +282,11 @@ type Event { """ usersAffected: Int + """ + Task Manager item linked to this event (e.g., GitHub Issue) + """ + taskManagerItem: TaskManagerItem + """ Return graph of the error rate for the specified period """ @@ -337,9 +342,9 @@ type Subscription { """ Sends new events from all user projects """ - eventOccurred: Event! + eventOccurred: Event! } - + """ Event information per day with these events """ @@ -453,7 +458,7 @@ extend type Mutation { Mark to set """ mark: EventMark! - ): Boolean! + ): Boolean! """ Namespace that contains only mutations related to the events diff --git a/src/typeDefs/eventTaskManagerItem.ts b/src/typeDefs/eventTaskManagerItem.ts new file mode 100644 index 00000000..9858dac8 --- /dev/null +++ b/src/typeDefs/eventTaskManagerItem.ts @@ -0,0 +1,43 @@ +import { gql } from 'apollo-server-express'; + +export default gql` +""" +Task Manager item linked to an event (e.g., GitHub Issue) +""" +type TaskManagerItem { + """ + Type of task manager item (currently only 'github-issue' is supported) + """ + type: String! + + """ + Task number (e.g., GitHub Issue number) + """ + number: Int! + + """ + URL to the task (e.g., GitHub Issue URL) + """ + url: String! + + """ + Task title + """ + title: String! + + """ + How the task was created (automatically by worker or manually by user) + """ + createdBy: String! + + """ + Task creation timestamp + """ + createdAt: DateTime! + + """ + Agent assigned to the task (e.g., 'copilot' for GitHub Copilot) + """ + assignee: String +} +`; diff --git a/src/typeDefs/index.ts b/src/typeDefs/index.ts index 8eca3893..e1784b14 100644 --- a/src/typeDefs/index.ts +++ b/src/typeDefs/index.ts @@ -2,6 +2,7 @@ import { gql } from 'apollo-server-express'; import billing from './billing'; import event from './event'; +import eventTaskManagerItem from './eventTaskManagerItem'; import notifications from './notifications'; import notificationsInput from './notificationsInput'; import projectNotifications from './projectNotifications'; @@ -90,6 +91,7 @@ const typeDefinitions = [ rootSchema, billing, event, + eventTaskManagerItem, notifications, notificationsInput, projectNotifications, From c66c159f7e15591280ae5f0fbccb4e9a4d33d3ee Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 02:15:36 +0300 Subject: [PATCH 07/15] Add GitHub OAuth integration and token management Introduces GitHub OAuth flow for user-to-server tokens, including endpoints for handling OAuth callbacks and exchanging codes for tokens. Updates the GitHub service to support OAuth code exchange, token validation, and refresh, and adds required environment variables for client ID and secret. Updates dependencies to support new OAuth methods and expands the project model to store task manager configuration. --- .env.sample | 5 + package.json | 5 +- src/integrations/github/routes.ts | 315 ++- src/integrations/github/service.ts | 204 +- src/models/project.ts | 7 +- yarn.lock | 3763 ++++++++++++---------------- 6 files changed, 2100 insertions(+), 2199 deletions(-) diff --git a/.env.sample b/.env.sample index 785e78c7..87180a6a 100644 --- a/.env.sample +++ b/.env.sample @@ -100,6 +100,9 @@ GITHUB_WEBHOOK_SECRET=623f6ed30b1f762803149893263a95cc2687fe3ce5a9f30648dcbf2571 # Id of GitHub app GITHUB_APP_ID=1234567 +# Client ID of GitHub app +GITHUB_APP_CLIENT_ID=Iv23li65HEIkWZXsm6qO + # GitHub App slug/name. Used to generate installation URLs GITHUB_APP_SLUG=hawk-tracker-app @@ -132,3 +135,5 @@ apxW9AHmx4aVRrmcIPq/BlMc6lGIgx2IwMBvJVpHVeUOUMAfNMRZV0XjY715xEyW U/uCfmCh8rfyQ75rthD4mGzNmHBpWrP/bD3c/vdj0wAxFXVyR5bG/Q== -----END RSA PRIVATE KEY-----" +# Generated in GitHub app settings +GITHUB_APP_CLIENT_SECRET=0663e20d484234e17b0871c1f070581739c14e04 diff --git a/package.json b/package.json index 1173eaaa..fe5280a2 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "eslint": "^6.7.2", "eslint-config-codex": "1.2.4", "eslint-plugin-import": "^2.19.1", - "jest": "^27.6.0", + "jest": "^30.2.0", "mongodb-memory-server": "^6.6.1", "nodemon": "^2.0.2", "redis-mock": "^0.56.3", @@ -43,9 +43,10 @@ "@graphql-tools/schema": "^8.5.1", "@graphql-tools/utils": "^8.9.0", "@hawk.so/nodejs": "^3.1.1", - "@hawk.so/types": "^0.4.2", + "@hawk.so/types": "^0.5.6", "@n1ru4l/json-patch-plus": "^0.2.0", "@node-saml/node-saml": "^5.0.1", + "@octokit/oauth-methods": "^4.0.0", "@octokit/rest": "^22.0.1", "@octokit/types": "^16.0.0", "@types/amqp-connection-manager": "^2.0.4", diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index 8331c11b..24b7997d 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -266,28 +266,156 @@ export function createGitHubRouter(factories: ContextFactories): express.Router /** * GET /integration/github/callback?state=&installation_id= * Handle GitHub App installation callback + * + * @deprecated - now we use /oauth endpoint for both installation and OAuth callbacks */ - router.get('/callback', async (req, res, next) => { + // router.get('/callback', async (req, res, next) => { + // try { + // const { state, installation_id } = req.query; + + // /** + // * Log callback request for debugging + // */ + // log('info', `Callback received: state=${state}, installation_id=${installation_id}, query=${JSON.stringify(req.query)}`); + + // /** + // * Validate required parameters + // */ + // if (!state || typeof state !== 'string') { + // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + // error: 'Missing or invalid state', + // })); + // } + + // if (!installation_id || typeof installation_id !== 'string') { + // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + // error: 'Missing or invalid installation_id parameter', + // })); + // } + + // /** + // * Verify state (CSRF protection) + // * getState() atomically gets and deletes the state, preventing reuse + // */ + // const stateData = await stateStore.getState(state); + + // if (!stateData) { + // log('warn', `Invalid or expired state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); + + // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + // error: 'Invalid or expired state. Please try connecting again.', + // })); + // } + + // const { projectId, userId } = stateData; + + // log('info', projectId, `Processing callback initiated by user ${sgr(userId, Effect.ForegroundCyan)}`); + + // /** + // * Verify project exists + // */ + // const project = await factories.projectsFactory.findById(projectId); + + // if (!project) { + // log('error', projectId, 'Project not found'); + + // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + // error: `Project not found: ${projectId}`, + // })); + // } + + // /** + // * Get installation info from GitHub + // */ + // let installation; + + // try { + // installation = await githubService.getInstallationForRepository(installation_id); + // log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`); + // } catch (error) { + // log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`); + + // return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + // error: 'Failed to retrieve GitHub installation information. Please try again.', + // })); + // } + + // /** + // * For now, we save only installationId + // * repoId and repoFullName will be set when creating the first issue or can be configured later + // * GitHub App installation can include multiple repositories, so we don't know which one to use yet + // */ + // const taskManagerConfig = { + // type: 'github', + // autoTaskEnabled: false, + // taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT, + // assignAgent: false, + // connectedAt: new Date(), + // updatedAt: new Date(), + // config: { + // installationId: installation_id, + // repoId: '', + // repoFullName: '', + // }, + // }; + + // let successRedirectUrl = buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + // success: 'true', + // }); + + // /** + // * Save taskManager configuration to project + // */ + // try { + // await project.updateProject(({ + // taskManager: taskManagerConfig, + // }) as any); + + // log('info', projectId, 'Successfully connected GitHub integration. Redirecting to ' + sgr(successRedirectUrl, Effect.ForegroundGreen)); + // } catch (error) { + // log('error', projectId, `Failed to save taskManager config: ${error instanceof Error ? error.message : String(error)}`); + + // return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + // error: 'Failed to save Task Manager configuration. Please try again.', + // })); + // } + + // /** + // * Redirect to Garage with success parameter + // */ + // return res.redirect(successRedirectUrl); + // } catch (error) { + // log('error', 'Error in /callback endpoint:', error); + // next(error); + // } + // }); + + /** + * GET /integration/github/oauth?code=&state=&installation_id= + * Handle GitHub OAuth callback for user-to-server token + * Also handles GitHub App installation if installation_id is present + */ + router.get('/oauth', async (req, res, next) => { try { - const { state, installation_id } = req.query; + const { code, state, installation_id } = req.query; /** - * Log callback request for debugging + * Log OAuth callback request for debugging */ - log('info', `Callback received: state=${state}, installation_id=${installation_id}, query=${JSON.stringify(req.query)}`); + log('info', `OAuth callback received: state=${state}, code=${code ? 'present' : 'missing'}, installation_id=${installation_id ? 'present' : 'missing'}, query=${JSON.stringify(req.query)}`); /** * Validate required parameters */ - if (!state || typeof state !== 'string') { + if (!code || typeof code !== 'string') { return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { - error: 'Missing or invalid state', + error: 'Missing or invalid OAuth code', })); } - if (!installation_id || typeof installation_id !== 'string') { + if (!state || typeof state !== 'string') { return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { - error: 'Missing or invalid installation_id parameter', + error: 'Missing or invalid state', })); } @@ -307,7 +435,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router const { projectId, userId } = stateData; - log('info', projectId, `Processing callback initiated by user ${sgr(userId, Effect.ForegroundCyan)}`); + log('info', projectId, `Processing OAuth callback initiated by user ${sgr(userId, Effect.ForegroundCyan)}`); /** * Verify project exists @@ -323,67 +451,170 @@ export function createGitHubRouter(factories: ContextFactories): express.Router } /** - * Get installation info from GitHub + * If installation_id is present, handle GitHub App installation first + * This happens when "Request user authorization (OAuth) during installation" is enabled */ - let installation; + if (installation_id && typeof installation_id === 'string') { + log('info', projectId, `GitHub App installation detected (installation_id: ${installation_id}), processing installation first`); + + /** + * Get installation info from GitHub + */ + let installation; + + try { + installation = await githubService.getInstallationForRepository(installation_id); + log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`); + } catch (error) { + log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`); + + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + error: 'Failed to retrieve GitHub installation information. Please try again.', + })); + } + + /** + * Create or update taskManager config with installation info + */ + const taskManagerConfig = { + type: 'github' as const, + autoTaskEnabled: false, + taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT, + assignAgent: false, + connectedAt: new Date(), + updatedAt: new Date(), + config: { + installationId: installation_id, + repoId: '', + repoFullName: '', + }, + }; + + try { + await project.updateProject({ + taskManager: project.taskManager ? { + ...project.taskManager, + ...taskManagerConfig, + config: { + ...project.taskManager.config, + installationId: installation_id, + }, + } : taskManagerConfig, + } as any); + + log('info', projectId, 'Successfully saved GitHub App installation'); + } catch (error) { + log('error', projectId, `Failed to save taskManager config: ${error instanceof Error ? error.message : String(error)}`); + + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + error: 'Failed to save Task Manager configuration. Please try again.', + })); + } + + /** + * Reload project to get updated taskManager config + */ + const updatedProject = await factories.projectsFactory.findById(projectId); + + if (!updatedProject) { + log('error', projectId, 'Project not found after update'); + + return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + error: `Project not found: ${projectId}`, + })); + } + + /** + * Use updated project for OAuth processing + */ + Object.assign(project, updatedProject); + } + + /** + * Verify project has taskManager config (should exist after installation or already exist) + */ + if (!project.taskManager) { + log('error', projectId, 'Project does not have taskManager config after installation'); + + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + error: 'GitHub App installation failed. Please try connecting again.', + })); + } + + /** + * Exchange OAuth code for user-to-server token + * This method already validates the token by calling getAuthenticated(), + * so no additional validation is needed + */ + let tokenData; try { - installation = await githubService.getInstallationForRepository(installation_id); - log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`); + tokenData = await githubService.exchangeOAuthCodeForToken(code); + log('info', projectId, `Successfully exchanged OAuth code for token for user ${sgr(tokenData.user.login, Effect.ForegroundCyan)}`); } catch (error) { - log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`); + log('error', projectId, `Failed to exchange OAuth code: ${error instanceof Error ? error.message : String(error)}`); return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { - error: 'Failed to retrieve GitHub installation information. Please try again.', + error: 'Failed to exchange OAuth code for token. Please try again.', })); } /** - * For now, we save only installationId - * repoId and repoFullName will be set when creating the first issue or can be configured later - * GitHub App installation can include multiple repositories, so we don't know which one to use yet + * Update project with delegatedUser token + * Token is already validated in exchangeOAuthCodeForToken() via getAuthenticated() */ - const taskManagerConfig = { - type: 'github', - autoTaskEnabled: false, - taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT, - assignAgent: false, - connectedAt: new Date(), - updatedAt: new Date(), - config: { - installationId: installation_id, - repoId: '', - repoFullName: '', - }, + const delegatedUser = { + hawkUserId: userId, + githubUserId: tokenData.user.id, + githubLogin: tokenData.user.login, + accessToken: tokenData.accessToken, + accessTokenExpiresAt: tokenData.expiresAt, + refreshToken: tokenData.refreshToken, + refreshTokenExpiresAt: tokenData.refreshTokenExpiresAt, + tokenCreatedAt: new Date(), + tokenLastValidatedAt: new Date(), // Token was validated in exchangeOAuthCodeForToken() + status: 'active' as const, }; - let successRedirectUrl = buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { - success: 'true', - }); - /** - * Save taskManager configuration to project + * Update taskManager config with delegatedUser + * Preserve existing config fields */ + const updatedTaskManager = { + ...project.taskManager, + config: { + ...project.taskManager.config, + delegatedUser, + }, + updatedAt: new Date(), + }; + try { - await project.updateProject(({ - taskManager: taskManagerConfig, - }) as any); + await project.updateProject({ + taskManager: updatedTaskManager, + } as any); - log('info', projectId, 'Successfully connected GitHub integration. Redirecting to ' + sgr(successRedirectUrl, Effect.ForegroundGreen)); + log('info', projectId, `Successfully saved delegatedUser token for user ${sgr(tokenData.user.login, Effect.ForegroundCyan)}`); } catch (error) { - log('error', projectId, `Failed to save taskManager config: ${error instanceof Error ? error.message : String(error)}`); + log('error', projectId, `Failed to save delegatedUser token: ${error instanceof Error ? error.message : String(error)}`); return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { - error: 'Failed to save Task Manager configuration. Please try again.', + error: 'Failed to save OAuth token. Please try again.', })); } /** * Redirect to Garage with success parameter */ + const successRedirectUrl = buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { + success: 'true', + }); + + log('info', projectId, 'OAuth authorization completed successfully. Redirecting to ' + sgr(successRedirectUrl, Effect.ForegroundGreen)); + return res.redirect(successRedirectUrl); } catch (error) { - log('error', 'Error in /callback endpoint:', error); + log('error', 'Error in /oauth endpoint:', error); next(error); } }); diff --git a/src/integrations/github/service.ts b/src/integrations/github/service.ts index efe739d5..ca843c5f 100644 --- a/src/integrations/github/service.ts +++ b/src/integrations/github/service.ts @@ -1,6 +1,7 @@ import jwt from 'jsonwebtoken'; import { Octokit } from '@octokit/rest'; import type { Endpoints } from '@octokit/types'; +import { exchangeWebFlowCode, refreshToken as refreshOAuthToken } from '@octokit/oauth-methods'; /** * Type for GitHub Issue creation parameters @@ -100,11 +101,23 @@ export class GitHubService { */ private readonly appId: string; + /** + * GitHub App Client ID from environment variables + * Required for OAuth token exchange (different from App ID) + */ + private readonly clientId: string; + /** * GitHub App slug/name from environment variables */ private readonly appSlug: string; + /** + * GitHub App Client Secret from environment variables + * Required for OAuth token exchange + */ + private readonly clientSecret: string; + /** * Default timeout for GitHub API requests (in milliseconds) * Increased from default 10s to 60s to handle slow network connections @@ -119,8 +132,18 @@ export class GitHubService { throw new Error('GITHUB_APP_ID environment variable is not set'); } + if (!process.env.GITHUB_APP_CLIENT_ID) { + throw new Error('GITHUB_APP_CLIENT_ID environment variable is not set'); + } + + if (!process.env.GITHUB_APP_CLIENT_SECRET) { + throw new Error('GITHUB_APP_CLIENT_SECRET environment variable is not set'); + } + this.appId = process.env.GITHUB_APP_ID; + this.clientId = process.env.GITHUB_APP_CLIENT_ID; this.appSlug = process.env.GITHUB_APP_SLUG || 'hawk-tracker'; + this.clientSecret = process.env.GITHUB_APP_CLIENT_SECRET; } /** @@ -166,8 +189,10 @@ export class GitHubService { * Form callback URL based on API_URL environment variable * This allows different callback URLs for different environments (dev, staging, production) * The redirect_url parameter ensures GitHub redirects to our callback with state preserved + * Note: When "Request user authorization (OAuth) during installation" is enabled, + * GitHub redirects to /oauth with both installation_id and code parameters */ - const redirectUrl = `${process.env.API_URL}/integration/github/callback`; + const redirectUrl = `${process.env.API_URL}/integration/github/oauth`; /** * Include both state and redirect_url parameters @@ -403,13 +428,14 @@ export class GitHubService { try { /** - * Assign GitHub Copilot (github-copilot[bot]) as assignee + * Assign GitHub Copilot coding agent (copilot-swe-agent[bot]) as assignee + * According to GitHub docs: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-a-pr */ await octokit.rest.issues.addAssignees({ owner, repo, issue_number: issueNumber, - assignees: ['github-copilot[bot]'], + assignees: ['copilot-swe-agent[bot]'], }); return true; @@ -502,4 +528,176 @@ export class GitHubService { throw new Error(`Failed to create installation token: ${error instanceof Error ? error.message : String(error)}`); } } + + /** + * Exchange OAuth authorization code for user-to-server access token + * This token allows the GitHub App to perform actions on behalf of the user + * + * @param code - OAuth authorization code from GitHub callback + * @param redirectUri - Redirect URI that was used in the OAuth authorization request (must match) + * @returns Tokens and user info + * @throws If token exchange fails + */ + public async exchangeOAuthCodeForToken( + code: string, + redirectUri?: string + ): Promise<{ + accessToken: string; + refreshToken: string; + expiresAt: Date | null; + refreshTokenExpiresAt: Date | null; + user: { id: number; login: string }; + }> { + try { + /** + * Build redirect URI if not provided + */ + if (!redirectUri) { + if (!process.env.API_URL) { + throw new Error('API_URL environment variable must be set to generate redirect URI'); + } + + redirectUri = `${process.env.API_URL}/integration/github/oauth`; + } + + /** + * Use Octokit OAuth methods for token exchange + * This is the recommended way to exchange OAuth code for access token + */ + const { authentication } = await exchangeWebFlowCode({ + clientType: 'github-app', + clientId: this.clientId, + clientSecret: this.clientSecret, + code, + redirectUrl: redirectUri, + }); + + + if (!authentication.token) { + throw new Error('No access token in OAuth response'); + } + + const accessToken = authentication.token; + /** + * refreshToken, expiresAt, and refreshTokenExpiresAt are only available in certain authentication types + * Use type guards to safely access these properties + */ + const refreshToken = 'refreshToken' in authentication && authentication.refreshToken + ? authentication.refreshToken + : ''; + const expiresAt = 'expiresAt' in authentication && authentication.expiresAt + ? new Date(authentication.expiresAt) + : null; + const refreshTokenExpiresAt = 'refreshTokenExpiresAt' in authentication && authentication.refreshTokenExpiresAt + ? new Date(authentication.refreshTokenExpiresAt) + : null; + + /** + * Get user info using the access token + */ + const octokit = this.createOctokit(accessToken); + const { data: userData } = await octokit.rest.users.getAuthenticated(); + + return { + accessToken, + refreshToken, + expiresAt, + refreshTokenExpiresAt, + user: { + id: userData.id, + login: userData.login, + }, + }; + } catch (error) { + throw new Error(`Failed to exchange OAuth code for token: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Validate user-to-server access token by making GET /user request + * Updates tokenLastValidatedAt if validation succeeds + * + * @param {string} accessToken - User-to-server access token + * @returns {Promise<{ valid: boolean; user?: { id: number; login: string }; status: 'active' | 'revoked' }>} Validation result + */ + public async validateUserToken(accessToken: string): Promise<{ valid: boolean; user?: { id: number; login: string }; status: 'active' | 'revoked' }> { + try { + const octokit = this.createOctokit(accessToken); + const { data: userData } = await octokit.rest.users.getAuthenticated(); + + return { + valid: true, + user: { + id: userData.id, + login: userData.login, + }, + status: 'active', + }; + } catch (error: any) { + /** + * Check if error is 401 or 403 (token revoked/invalid) + */ + if (error?.status === 401 || error?.status === 403) { + return { + valid: false, + status: 'revoked', + }; + } + + /** + * Other errors (network, etc.) - consider token as potentially valid + * but log the error + */ + throw new Error(`Failed to validate user token: ${error instanceof Error ? error.message : String(error)}`); + } + } + + /** + * Refresh user-to-server access token using refresh token + * Rotates refresh token if a new one is provided + * + * @param {string} refreshToken - OAuth refresh token + * @returns {Promise<{ accessToken: string; refreshToken: string; expiresAt: Date | null; refreshTokenExpiresAt: Date | null }>} New tokens + * @throws {Error} If token refresh fails + */ + public async refreshUserToken(refreshToken: string): Promise<{ + accessToken: string; + refreshToken: string; + expiresAt: Date | null; + refreshTokenExpiresAt: Date | null; + }> { + try { + const { authentication } = await refreshOAuthToken({ + clientType: 'github-app', + clientId: this.clientId, + clientSecret: this.clientSecret, + refreshToken, + }); + + if (!authentication.token) { + throw new Error('No access token in refresh response'); + } + + /** + * refreshToken is only available in GitHubAppAuthenticationWithRefreshToken type + * Check if it exists before accessing + */ + const newRefreshToken = 'refreshToken' in authentication + ? authentication.refreshToken || refreshToken + : refreshToken; // Use new refresh token if provided, otherwise keep old one + + return { + accessToken: authentication.token, + refreshToken: newRefreshToken, + expiresAt: 'expiresAt' in authentication && authentication.expiresAt + ? new Date(authentication.expiresAt) + : null, + refreshTokenExpiresAt: 'refreshTokenExpiresAt' in authentication && authentication.refreshTokenExpiresAt + ? new Date(authentication.refreshTokenExpiresAt) + : null, + }; + } catch (error) { + throw new Error(`Failed to refresh user token: ${error instanceof Error ? error.message : String(error)}`); + } + } } diff --git a/src/models/project.ts b/src/models/project.ts index ce788e7a..3b8dacce 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -1,7 +1,7 @@ import { Collection, ObjectId } from 'mongodb'; import AbstractModel from './abstractModel'; import { NotificationsChannelsDBScheme } from '../types/notification-channels'; -import { ProjectDBScheme, ProjectNotificationsRuleDBScheme, ProjectEventGroupingPatternsDBScheme } from '@hawk.so/types'; +import { ProjectDBScheme, ProjectNotificationsRuleDBScheme, ProjectEventGroupingPatternsDBScheme, ProjectTaskManagerConfig } from '@hawk.so/types'; import { v4 as uuid } from 'uuid'; /** @@ -193,6 +193,11 @@ export default class ProjectModel extends AbstractModel impleme */ public eventGroupingPatterns!: ProjectEventGroupingPatternsDBScheme[]; + /** + * Task Manager configuration + */ + public taskManager?: ProjectTaskManagerConfig; + /** * Model's collection */ diff --git a/yarn.lock b/yarn.lock index e4ef4f98..612281e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -67,14 +67,6 @@ "@amplitude/types" "^1.10.0" tslib "^1.9.3" -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - "@apollo/protobufjs@1.2.4": version "1.2.4" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.4.tgz#d913e7627210ec5efd758ceeb751c776c68ba133" @@ -158,136 +150,129 @@ dependencies: xss "^1.0.8" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.18.6": +"@babel/code-frame@^7.0.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" - integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== - -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59" - integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.9" - "@babel/helper-compilation-targets" "^7.18.9" - "@babel/helper-module-transforms" "^7.18.9" - "@babel/helpers" "^7.18.9" - "@babel/parser" "^7.18.9" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" - convert-source-map "^1.7.0" +"@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" + integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.6.tgz#103f466803fa0f059e82ccac271475470570d74c" + integrity sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg== + +"@babel/core@^7.23.9", "@babel/core@^7.27.4": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.6.tgz#531bf883a1126e53501ba46eb3bb414047af507f" + integrity sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/generator@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.9.tgz#68337e9ea8044d6ddc690fb29acae39359cca0a5" - integrity sha512-wt5Naw6lJrL1/SGkipMiFxJjtyczUWTP38deiP1PO60HsBjDeKk08CGC3S8iVuvf0FmTdgKwU1KIXzSKL1G0Ug== - dependencies: - "@babel/types" "^7.18.9" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/helper-compilation-targets@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" - integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== - dependencies: - "@babel/compat-data" "^7.18.8" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" - semver "^6.3.0" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-function-name@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" - integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== - dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.9" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" - integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.27.5", "@babel/generator@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.6.tgz#48dcc65d98fcc8626a48f72b62e263d25fc3c3f1" + integrity sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw== + dependencies: + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== - dependencies: - "@babel/types" "^7.18.6" +"@babel/helper-plugin-utils@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8" + integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug== -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== -"@babel/helpers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" - integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.6.tgz#fca903a313ae675617936e8998b814c415cbf5d7" + integrity sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw== dependencies: - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" "@babel/highlight@^7.18.6": version "7.18.6" @@ -298,11 +283,18 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.9": +"@babel/parser@^7.1.0": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539" integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg== +"@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.6.tgz#f01a8885b7fa1e56dd8a155130226cd698ef13fd" + integrity sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ== + dependencies: + "@babel/types" "^7.28.6" + "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -317,14 +309,28 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-class-properties@^7.8.3": +"@babel/plugin-syntax-class-properties@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503" + integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-import-meta@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -338,7 +344,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": +"@babel/plugin-syntax-jsx@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee" + integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w== + dependencies: + "@babel/helper-plugin-utils" "^7.28.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== @@ -352,7 +365,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.8.3": +"@babel/plugin-syntax-numeric-separator@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== @@ -380,39 +393,50 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.8.3": +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/template@^7.18.6", "@babel/template@^7.3.3": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" - integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== +"@babel/plugin-syntax-typescript@^7.27.1": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2" + integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.6" - "@babel/types" "^7.18.6" + "@babel/helper-plugin-utils" "^7.28.6" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98" - integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.18.9" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.18.9" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.18.9" - "@babel/types" "^7.18.9" - debug "^4.1.0" - globals "^11.1.0" +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.6.tgz#871ddc79a80599a5030c53b1cc48cbe3a5583c2e" + integrity sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/generator" "^7.28.6" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.6" + "@babel/template" "^7.28.6" + "@babel/types" "^7.28.6" + debug "^4.3.1" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3": +"@babel/types@^7.0.0", "@babel/types@^7.3.0": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.9.tgz#7148d64ba133d8d73a41b3172ac4b83a1452205f" integrity sha512-WwMLAg2MvJmt/rKEVQBBhIVffMmnilX4oe0sRe7iPOHIGsqpruFHHdrfj4O1CMMtgMtCU4oPafZjDPCRgO57Wg== @@ -420,19 +444,19 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" +"@babel/types@^7.20.7", "@babel/types@^7.27.3", "@babel/types@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.6.tgz#c3e9377f1b155005bcc4c46020e7e394e13089df" + integrity sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -440,6 +464,28 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@emnapi/core@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.8.1.tgz#fd9efe721a616288345ffee17a1f26ac5dd01349" + integrity sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.8.1.tgz#550fa7e3c0d49c5fb175a116e8cd70614f9a22a5" + integrity sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + "@epic-web/invariant@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@epic-web/invariant/-/invariant-1.0.0.tgz#1073e5dee6dd540410784990eb73e4acd25c9813" @@ -496,13 +542,25 @@ dependencies: "@types/mongodb" "^3.5.34" -"@hawk.so/types@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.4.2.tgz#85482495a951de47ba8be88725d56ab2d72184bc" - integrity sha512-0eY/XYhloRiTq3M7d76WrRToVsDjSxXP/gMtBbbNc0qo9RFjmrS4JKsNx52EQVPdBBdi6s6njcGF1DrhYCrQUA== +"@hawk.so/types@^0.5.6": + version "0.5.6" + resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.5.6.tgz#1fbd06a79de32595936c817ff416471c0767bd5a" + integrity sha512-oPoi0Zf2GZDh0OdEd+imw9VAIJcp9zwtk3jLVBOvXcX+LbTKOt0kwkcblacQpsTFB1ljleVQ15gULnV3qbHCLw== dependencies: bson "^7.0.0" +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -514,56 +572,56 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/schema@^0.1.2": +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== +"@jest/console@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-30.2.0.tgz#c52fcd5b58fdd2e8eb66b2fd8ae56f2f64d05b28" + integrity sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "30.2.0" "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" + chalk "^4.1.2" + jest-message-util "30.2.0" + jest-util "30.2.0" slash "^3.0.0" -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== +"@jest/core@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-30.2.0.tgz#813d59faa5abd5510964a8b3a7b17cc77b775275" + integrity sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "30.2.0" + "@jest/pattern" "30.0.1" + "@jest/reporters" "30.2.0" + "@jest/test-result" "30.2.0" + "@jest/transform" "30.2.0" + "@jest/types" "30.2.0" "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" - rimraf "^3.0.0" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + ci-info "^4.2.0" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-changed-files "30.2.0" + jest-config "30.2.0" + jest-haste-map "30.2.0" + jest-message-util "30.2.0" + jest-regex-util "30.0.1" + jest-resolve "30.2.0" + jest-resolve-dependencies "30.2.0" + jest-runner "30.2.0" + jest-runtime "30.2.0" + jest-snapshot "30.2.0" + jest-util "30.2.0" + jest-validate "30.2.0" + jest-watcher "30.2.0" + micromatch "^4.0.8" + pretty-format "30.2.0" slash "^3.0.0" - strip-ansi "^6.0.0" "@jest/create-cache-key-function@^30.0.0": version "30.2.0" @@ -572,36 +630,62 @@ dependencies: "@jest/types" "30.2.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== +"@jest/diff-sequences@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be" + integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw== + +"@jest/environment@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-30.2.0.tgz#1e673cdb8b93ded707cf6631b8353011460831fa" + integrity sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g== dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/fake-timers" "30.2.0" + "@jest/types" "30.2.0" "@types/node" "*" - jest-mock "^26.6.2" + jest-mock "30.2.0" + +"@jest/expect-utils@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz#4f95413d4748454fdb17404bf1141827d15e6011" + integrity sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA== + dependencies: + "@jest/get-type" "30.1.0" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== +"@jest/expect@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.2.0.tgz#9a5968499bb8add2bbb09136f69f7df5ddbf3185" + integrity sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA== + dependencies: + expect "30.2.0" + jest-snapshot "30.2.0" + +"@jest/fake-timers@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-30.2.0.tgz#0941ddc28a339b9819542495b5408622dc9e94ec" + integrity sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw== dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" + "@jest/types" "30.2.0" + "@sinonjs/fake-timers" "^13.0.0" "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-message-util "30.2.0" + jest-mock "30.2.0" + jest-util "30.2.0" + +"@jest/get-type@30.1.0": + version "30.1.0" + resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc" + integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA== -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== +"@jest/globals@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-30.2.0.tgz#2f4b696d5862664b89c4ee2e49ae24d2bb7e0988" + integrity sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw== dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" + "@jest/environment" "30.2.0" + "@jest/expect" "30.2.0" + "@jest/types" "30.2.0" + jest-mock "30.2.0" "@jest/pattern@30.0.1": version "30.0.1" @@ -611,37 +695,34 @@ "@types/node" "*" jest-regex-util "30.0.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== +"@jest/reporters@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-30.2.0.tgz#a36b28fcbaf0c4595250b108e6f20e363348fd91" + integrity sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" + "@jest/console" "30.2.0" + "@jest/test-result" "30.2.0" + "@jest/transform" "30.2.0" + "@jest/types" "30.2.0" + "@jridgewell/trace-mapping" "^0.3.25" + "@types/node" "*" + chalk "^4.1.2" + collect-v8-coverage "^1.0.2" + exit-x "^0.2.2" + glob "^10.3.10" + graceful-fs "^4.2.11" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" + istanbul-lib-source-maps "^5.0.0" + istanbul-reports "^3.1.3" + jest-message-util "30.2.0" + jest-util "30.2.0" + jest-worker "30.2.0" slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" + string-length "^4.0.2" + v8-to-istanbul "^9.0.1" "@jest/schemas@30.0.5": version "30.0.5" @@ -650,56 +731,65 @@ dependencies: "@sinclair/typebox" "^0.34.0" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== +"@jest/snapshot-utils@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz#387858eb90c2f98f67bff327435a532ac5309fbe" + integrity sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug== + dependencies: + "@jest/types" "30.2.0" + chalk "^4.1.2" + graceful-fs "^4.2.11" + natural-compare "^1.4.0" + +"@jest/source-map@30.0.1": + version "30.0.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-30.0.1.tgz#305ebec50468f13e658b3d5c26f85107a5620aaa" + integrity sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg== dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.4" - source-map "^0.6.0" + "@jridgewell/trace-mapping" "^0.3.25" + callsites "^3.1.0" + graceful-fs "^4.2.11" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== +"@jest/test-result@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-30.2.0.tgz#9c0124377fb7996cdffb86eda3dbc56eacab363d" + integrity sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg== dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== - dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - -"@jest/transform@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" - integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.6.2" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-regex-util "^26.0.0" - jest-util "^26.6.2" - micromatch "^4.0.2" - pirates "^4.0.1" + "@jest/console" "30.2.0" + "@jest/types" "30.2.0" + "@types/istanbul-lib-coverage" "^2.0.6" + collect-v8-coverage "^1.0.2" + +"@jest/test-sequencer@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz#bf0066bc72e176d58f5dfa7f212b6e7eee44f221" + integrity sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q== + dependencies: + "@jest/test-result" "30.2.0" + graceful-fs "^4.2.11" + jest-haste-map "30.2.0" + slash "^3.0.0" + +"@jest/transform@30.2.0": + version "30.2.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-30.2.0.tgz#54bef1a4510dcbd58d5d4de4fe2980a63077ef2a" + integrity sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA== + dependencies: + "@babel/core" "^7.27.4" + "@jest/types" "30.2.0" + "@jridgewell/trace-mapping" "^0.3.25" + babel-plugin-istanbul "^7.0.1" + chalk "^4.1.2" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.11" + jest-haste-map "30.2.0" + jest-regex-util "30.0.1" + jest-util "30.2.0" + micromatch "^4.0.8" + pirates "^4.0.7" slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" + write-file-atomic "^5.0.1" "@jest/types@30.2.0": version "30.2.0" @@ -714,15 +804,15 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" - "@types/yargs" "^15.0.0" + "@types/yargs" "^16.0.0" chalk "^4.0.0" "@josephg/resolvable@^1.0.0": @@ -730,38 +820,42 @@ resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" @@ -770,13 +864,13 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.14" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" - integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@mongodb-js/saslprep@^1.3.0": version "1.4.4" @@ -790,6 +884,15 @@ resolved "https://registry.yarnpkg.com/@n1ru4l/json-patch-plus/-/json-patch-plus-0.2.0.tgz#b8fa09fd980c3460dfdc109a7c4cc5590157aa6b" integrity sha512-pLkJy83/rVfDTyQgDSC8GeXAHEdXNHGNJrB1b7wAyGQu0iv7tpMXntKVSqj0+XKNVQbco40SZffNfVALzIt0SQ== +"@napi-rs/wasm-runtime@^0.2.11": + version "0.2.12" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + "@node-saml/node-saml@^5.0.1": version "5.1.0" resolved "https://registry.yarnpkg.com/@node-saml/node-saml/-/node-saml-5.1.0.tgz#43d61d4ea882f2960a44c7be5ae0030dafea2382" @@ -834,6 +937,14 @@ "@octokit/types" "^16.0.0" universal-user-agent "^7.0.2" +"@octokit/endpoint@^9.0.6": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.6.tgz#114d912108fe692d8b139cfe7fc0846dfd11b6c0" + integrity sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw== + dependencies: + "@octokit/types" "^13.1.0" + universal-user-agent "^6.0.0" + "@octokit/graphql@^9.0.3": version "9.0.3" resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-9.0.3.tgz#5b8341c225909e924b466705c13477face869456" @@ -843,6 +954,27 @@ "@octokit/types" "^16.0.0" universal-user-agent "^7.0.0" +"@octokit/oauth-authorization-url@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz#cc82ca29cc5e339c9921672f39f2b3f5c8eb6ef2" + integrity sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA== + +"@octokit/oauth-methods@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz#1403ac9c4d4e277922fddc4c89fa8a782f8f791b" + integrity sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw== + dependencies: + "@octokit/oauth-authorization-url" "^6.0.2" + "@octokit/request" "^8.3.1" + "@octokit/request-error" "^5.1.0" + "@octokit/types" "^13.0.0" + btoa-lite "^1.0.0" + +"@octokit/openapi-types@^24.2.0": + version "24.2.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-24.2.0.tgz#3d55c32eac0d38da1a7083a9c3b0cca77924f7d3" + integrity sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg== + "@octokit/openapi-types@^27.0.0": version "27.0.0" resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-27.0.0.tgz#374ea53781965fd02a9d36cacb97e152cefff12d" @@ -867,6 +999,15 @@ dependencies: "@octokit/types" "^16.0.0" +"@octokit/request-error@^5.1.0", "@octokit/request-error@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.1.1.tgz#b9218f9c1166e68bb4d0c89b638edc62c9334805" + integrity sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g== + dependencies: + "@octokit/types" "^13.1.0" + deprecation "^2.0.0" + once "^1.4.0" + "@octokit/request-error@^7.0.2": version "7.1.0" resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-7.1.0.tgz#440fa3cae310466889778f5a222b47a580743638" @@ -885,6 +1026,16 @@ fast-content-type-parse "^3.0.0" universal-user-agent "^7.0.2" +"@octokit/request@^8.3.1": + version "8.4.1" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.4.1.tgz#715a015ccf993087977ea4365c44791fc4572486" + integrity sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw== + dependencies: + "@octokit/endpoint" "^9.0.6" + "@octokit/request-error" "^5.1.1" + "@octokit/types" "^13.1.0" + universal-user-agent "^6.0.0" + "@octokit/rest@^22.0.1": version "22.0.1" resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-22.0.1.tgz#4d866c32b76b711d3f736f91992e2b534163b416" @@ -895,6 +1046,13 @@ "@octokit/plugin-request-log" "^6.0.0" "@octokit/plugin-rest-endpoint-methods" "^17.0.0" +"@octokit/types@^13.0.0", "@octokit/types@^13.1.0": + version "13.10.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.10.0.tgz#3e7c6b19c0236c270656e4ea666148c2b51fd1a3" + integrity sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA== + dependencies: + "@octokit/openapi-types" "^24.2.0" + "@octokit/types@^16.0.0": version "16.0.0" resolved "https://registry.yarnpkg.com/@octokit/types/-/types-16.0.0.tgz#fbd7fa590c2ef22af881b1d79758bfaa234dbb7c" @@ -912,6 +1070,16 @@ resolved "https://registry.yarnpkg.com/@phc/format/-/format-1.0.0.tgz#b5627003b3216dc4362125b13f48a4daa76680e4" integrity sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + +"@pkgr/core@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" + integrity sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA== + "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" @@ -1013,93 +1181,93 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g== -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== +"@sinonjs/commons@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== +"@sinonjs/fake-timers@^13.0.0": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^3.0.1" "@standard-schema/spec@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== -"@swc/core-darwin-arm64@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.7.tgz#b7ac4660d2d4df324f4f5a6f58a0c3d44d71ff1c" - integrity sha512-+hNVUfezUid7LeSHqnhoC6Gh3BROABxjlDNInuZ/fie1RUxaEX4qzDwdTgozJELgHhvYxyPIg1ro8ibnKtgO4g== - -"@swc/core-darwin-x64@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.7.tgz#4500d361b389459e32a5acacf89426d4865761c2" - integrity sha512-ZAFuvtSYZTuXPcrhanaD5eyp27H8LlDzx2NAeVyH0FchYcuXf0h5/k3GL9ZU6Jw9eQ63R1E8KBgpXEJlgRwZUQ== - -"@swc/core-linux-arm-gnueabihf@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.7.tgz#2f7a8dcdf1880a03f38f0a4a2814a254bdf07684" - integrity sha512-K3HTYocpqnOw8KcD8SBFxiDHjIma7G/X+bLdfWqf+qzETNBrzOub/IEkq9UaeupaJiZJkPptr/2EhEXXWryS/A== - -"@swc/core-linux-arm64-gnu@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.7.tgz#dba63e6a5ae6139588312049f1897208c2d94c14" - integrity sha512-HCnVIlsLnCtQ3uXcXgWrvQ6SAraskLA9QJo9ykTnqTH6TvUYqEta+TdTdGjzngD6TOE7XjlAiUs/RBtU8Z0t+Q== - -"@swc/core-linux-arm64-musl@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.7.tgz#f23572b6d2f6d18e691111f1f238d32861e22528" - integrity sha512-/OOp9UZBg4v2q9+x/U21Jtld0Wb8ghzBScwhscI7YvoSh4E8RALaJ1msV8V8AKkBkZH7FUAFB7Vbv0oVzZsezA== - -"@swc/core-linux-x64-gnu@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.7.tgz#7efa998cb3199f282583f5055ae3396daba0ba81" - integrity sha512-VBbs4gtD4XQxrHuQ2/2+TDZpPQQgrOHYRnS6SyJW+dw0Nj/OomRqH+n5Z4e/TgKRRbieufipeIGvADYC/90PYQ== - -"@swc/core-linux-x64-musl@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.7.tgz#d5c88a8c367baf9f6b4215e8f6c9eae680451087" - integrity sha512-kVuy2unodso6p0rMauS2zby8/bhzoGRYxBDyD6i2tls/fEYAE74oP0VPFzxIyHaIjK1SN6u5TgvV9MpyJ5xVug== - -"@swc/core-win32-arm64-msvc@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.7.tgz#08e175725f0796337e3299b94b701ebb7bfdc14f" - integrity sha512-uddYoo5Xmo1XKLhAnh4NBIyy5d0xk33x1sX3nIJboFySLNz878ksCFCZ3IBqrt1Za0gaoIWoOSSSk0eNhAc/sw== - -"@swc/core-win32-ia32-msvc@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.7.tgz#6ad4aeff1bd39f0482e905719f8390d53b072854" - integrity sha512-rqq8JjNMLx3QNlh0aPTtN/4+BGLEHC94rj9mkH1stoNRf3ra6IksNHMHy+V1HUqElEgcZyx+0yeXx3eLOTcoFw== - -"@swc/core-win32-x64-msvc@1.15.7": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.7.tgz#52f7d558144cee9a3cc43ba7e6bd8b83a8acae77" - integrity sha512-4BK06EGdPnuplgcNhmSbOIiLdRgHYX3v1nl4HXo5uo4GZMfllXaCyBUes+0ePRfwbn9OFgVhCWPcYYjMT6hycQ== +"@swc/core-darwin-arm64@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.10.tgz#fb76ac44fa1d30696299d45c84f35b62f9ea2bac" + integrity sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA== + +"@swc/core-darwin-x64@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.10.tgz#b00fdc59022152cc4998e8478a9250ca8382a7d7" + integrity sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ== + +"@swc/core-linux-arm-gnueabihf@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.10.tgz#d39405a2ddcf6604db684fcbbcc9c807810b5d41" + integrity sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg== + +"@swc/core-linux-arm64-gnu@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.10.tgz#5f59553f7d1d3e0af02eb9544b6f88c05a7bb372" + integrity sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ== + +"@swc/core-linux-arm64-musl@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.10.tgz#eb833e49df2a706d2ccf8e4474c111e7a1b784a5" + integrity sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw== + +"@swc/core-linux-x64-gnu@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.10.tgz#5d0b6f36e9d9d49fc0a2170fec2b6de2c9c0e76a" + integrity sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg== + +"@swc/core-linux-x64-musl@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.10.tgz#0788cf24330f8efec0379cce594c727f3bc0a7c8" + integrity sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw== + +"@swc/core-win32-arm64-msvc@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.10.tgz#1555dc561606268ff84805568cd9a60b5de42543" + integrity sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw== + +"@swc/core-win32-ia32-msvc@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.10.tgz#e51f79e4e257f9b1a8c90437b20fc5a9b5b13119" + integrity sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw== + +"@swc/core-win32-x64-msvc@1.15.10": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.10.tgz#9c2a11ac3321ebea8fc24940a1cd4b4cc9249fe2" + integrity sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA== "@swc/core@^1.3.0": - version "1.15.7" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.7.tgz#630a90c5801c7ed3dfd25d94da6b1eab22e52971" - integrity sha512-kTGB8XI7P+pTKW83tnUEDVP4zduF951u3UAOn5eTi0vyW6MvL56A3+ggMdfuVFtDI0/DsbSzf5z34HVBbuScWw== + version "1.15.10" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.10.tgz#8487c006be73f4b4bee52d431f2b29881ae70ea4" + integrity sha512-udNofxftduMUEv7nqahl2nvodCiCDQ4Ge0ebzsEm6P8s0RC2tBM0Hqx0nNF5J/6t9uagFJyWIDjXy3IIWMHDJw== dependencies: "@swc/counter" "^0.1.3" "@swc/types" "^0.1.25" optionalDependencies: - "@swc/core-darwin-arm64" "1.15.7" - "@swc/core-darwin-x64" "1.15.7" - "@swc/core-linux-arm-gnueabihf" "1.15.7" - "@swc/core-linux-arm64-gnu" "1.15.7" - "@swc/core-linux-arm64-musl" "1.15.7" - "@swc/core-linux-x64-gnu" "1.15.7" - "@swc/core-linux-x64-musl" "1.15.7" - "@swc/core-win32-arm64-msvc" "1.15.7" - "@swc/core-win32-ia32-msvc" "1.15.7" - "@swc/core-win32-x64-msvc" "1.15.7" + "@swc/core-darwin-arm64" "1.15.10" + "@swc/core-darwin-x64" "1.15.10" + "@swc/core-linux-arm-gnueabihf" "1.15.10" + "@swc/core-linux-arm64-gnu" "1.15.10" + "@swc/core-linux-arm64-musl" "1.15.10" + "@swc/core-linux-x64-gnu" "1.15.10" + "@swc/core-linux-x64-musl" "1.15.10" + "@swc/core-win32-arm64-msvc" "1.15.10" + "@swc/core-win32-ia32-msvc" "1.15.10" + "@swc/core-win32-x64-msvc" "1.15.10" "@swc/counter@^0.1.3": version "0.1.3" @@ -1122,11 +1290,6 @@ dependencies: "@swc/counter" "^0.1.3" -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -1147,6 +1310,13 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== +"@tybys/wasm-util@^0.10.0": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz#ecddd3205cf1e2d5274649ff0eedd2991ed7f414" + integrity sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg== + dependencies: + tslib "^2.4.0" + "@types/accepts@*", "@types/accepts@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" @@ -1169,13 +1339,13 @@ "@types/bluebird" "*" "@types/node" "*" -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": - version "7.1.19" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" "@types/babel__generator" "*" "@types/babel__template" "*" "@types/babel__traverse" "*" @@ -1195,7 +1365,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": +"@types/babel__traverse@*": version "7.17.1" resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== @@ -1308,13 +1478,6 @@ dependencies: "@types/node" "*" -"@types/graceful-fs@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== - dependencies: - "@types/node" "*" - "@types/graphql-upload@^8.0.11": version "8.0.11" resolved "https://registry.yarnpkg.com/@types/graphql-upload/-/graphql-upload-8.0.11.tgz#0fef061be3dfc1f370d86cbc28bcb6a968c6b003" @@ -1366,13 +1529,13 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^26.0.8": - version "26.0.24" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" - integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== +"@types/jest@^27.5.0": + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== dependencies: - jest-diff "^26.0.0" - pretty-format "^26.0.0" + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" "@types/json-schema@^7.0.3": version "7.0.11" @@ -1504,21 +1667,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.123.tgz#9073e454ee52ce9e2de038e7e0cf90f65c9abd56" integrity sha512-/n7I6V/4agSpJtFDKKFEa763Hc1z3hmvchobHS1TisCOTKD5nxq8NJ2iK7SRIMYL276Q9mgWOx2AWp5n2XI6eA== -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - "@types/object-hash@^2.1.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-2.2.1.tgz#67c169f8f033e0b62abbf81df2d00f4598d540b9" integrity sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ== -"@types/prettier@^2.0.0": - version "2.6.4" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.6.4.tgz#ad899dad022bab6b5a9f0a0fe67c2f7a4a8950ed" - integrity sha512-fOwvpvQYStpb/zHMx0Cauwywu9yLDmzWiiQBC7gJyq5tYLUXFZvDG7VK1B7WBxxjBJNKFOZ0zLoOQn8vmATbhw== - "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -1552,10 +1705,10 @@ "@types/mime" "*" "@types/node" "*" -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stack-utils@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== "@types/strip-bom@^3.0.0": version "3.0.0" @@ -1608,10 +1761,10 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== +"@types/yargs@^16.0.0": + version "16.0.11" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.11.tgz#de958fb62e77fc383fa6cd8066eabdd13da88f04" + integrity sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g== dependencies: "@types/yargs-parser" "*" @@ -1665,6 +1818,108 @@ semver "^7.3.2" tsutils "^3.17.1" +"@ungap/structured-clone@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + +"@unrs/resolver-binding-android-arm-eabi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz#9f5b04503088e6a354295e8ea8fe3cb99e43af81" + integrity sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw== + +"@unrs/resolver-binding-android-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz#7414885431bd7178b989aedc4d25cccb3865bc9f" + integrity sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g== + +"@unrs/resolver-binding-darwin-arm64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz#b4a8556f42171fb9c9f7bac8235045e82aa0cbdf" + integrity sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g== + +"@unrs/resolver-binding-darwin-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz#fd4d81257b13f4d1a083890a6a17c00de571f0dc" + integrity sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ== + +"@unrs/resolver-binding-freebsd-x64@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz#d2513084d0f37c407757e22f32bd924a78cfd99b" + integrity sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw== + +"@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz#844d2605d057488d77fab09705f2866b86164e0a" + integrity sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw== + +"@unrs/resolver-binding-linux-arm-musleabihf@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz#204892995cefb6bd1d017d52d097193bc61ddad3" + integrity sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw== + +"@unrs/resolver-binding-linux-arm64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz#023eb0c3aac46066a10be7a3f362e7b34f3bdf9d" + integrity sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ== + +"@unrs/resolver-binding-linux-arm64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz#9e6f9abb06424e3140a60ac996139786f5d99be0" + integrity sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w== + +"@unrs/resolver-binding-linux-ppc64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz#b111417f17c9d1b02efbec8e08398f0c5527bb44" + integrity sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA== + +"@unrs/resolver-binding-linux-riscv64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz#92ffbf02748af3e99873945c9a8a5ead01d508a9" + integrity sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ== + +"@unrs/resolver-binding-linux-riscv64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz#0bec6f1258fc390e6b305e9ff44256cb207de165" + integrity sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew== + +"@unrs/resolver-binding-linux-s390x-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz#577843a084c5952f5906770633ccfb89dac9bc94" + integrity sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg== + +"@unrs/resolver-binding-linux-x64-gnu@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz#36fb318eebdd690f6da32ac5e0499a76fa881935" + integrity sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w== + +"@unrs/resolver-binding-linux-x64-musl@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz#bfb9af75f783f98f6a22c4244214efe4df1853d6" + integrity sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA== + +"@unrs/resolver-binding-wasm32-wasi@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz#752c359dd875684b27429500d88226d7cc72f71d" + integrity sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.11" + +"@unrs/resolver-binding-win32-arm64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz#ce5735e600e4c2fbb409cd051b3b7da4a399af35" + integrity sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw== + +"@unrs/resolver-binding-win32-ia32-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz#72fc57bc7c64ec5c3de0d64ee0d1810317bc60a6" + integrity sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ== + +"@unrs/resolver-binding-win32-x64-msvc@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777" + integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g== + "@vercel/oidc@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@vercel/oidc/-/oidc-3.0.3.tgz#82c2b6dd4d5c3b37dcb1189718cdeb9db402d052" @@ -1680,11 +1935,6 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608" integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw== -abab@^2.0.3, abab@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" - integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== - abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1698,24 +1948,11 @@ accepts@^1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - acorn-jsx@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" @@ -1726,7 +1963,7 @@ acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.2.4, acorn@^8.4.1: +acorn@^8.4.1: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -1782,7 +2019,7 @@ amqplib@^0.5.5: safe-buffer "~5.1.2" url-parse "~1.4.3" -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -1794,11 +2031,16 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== -ansi-regex@^5.0.0, ansi-regex@^5.0.1: +ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -1813,15 +2055,25 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== +ansi-styles@^5.0.0, ansi-styles@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-styles@^6.1.0: + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== + +anymatch@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" + normalize-path "^3.0.0" + picomatch "^2.0.4" -anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -1940,21 +2192,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -1971,11 +2208,6 @@ array-includes@^3.0.3, array-includes@^3.1.4: get-intrinsic "^1.1.1" is-string "^1.0.7" -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== - array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.5: version "1.3.0" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" @@ -1986,11 +2218,6 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.5: es-abstract "^1.19.2" es-shim-unscopables "^1.0.0" -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - astral-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" @@ -2027,11 +2254,6 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" @@ -2080,66 +2302,65 @@ b4a@^1.6.4: resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.7.3.tgz#24cf7ccda28f5465b66aec2bac69e32809bf112f" integrity sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== +babel-jest@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-30.2.0.tgz#fd44a1ec9552be35ead881f7381faa7d8f3b95ac" + integrity sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw== dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" + "@jest/transform" "30.2.0" + "@types/babel__core" "^7.20.5" + babel-plugin-istanbul "^7.0.1" + babel-preset-jest "30.2.0" + chalk "^4.1.2" + graceful-fs "^4.2.11" slash "^3.0.0" -babel-plugin-istanbul@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" - integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== +babel-plugin-istanbul@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz#d8b518c8ea199364cf84ccc82de89740236daf92" + integrity sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^5.0.4" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-instrument "^6.0.2" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== +babel-plugin-jest-hoist@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz#94c250d36b43f95900f3a219241e0f4648191ce2" + integrity sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA== dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" + "@types/babel__core" "^7.20.5" -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== +babel-preset-current-node-syntax@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6" + integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== +babel-preset-jest@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz#04717843e561347781d6d7f69c81e6bcc3ed11ce" + integrity sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ== dependencies: - babel-plugin-jest-hoist "^26.6.2" - babel-preset-current-node-syntax "^1.0.0" + babel-plugin-jest-hoist "30.2.0" + babel-preset-current-node-syntax "^1.2.0" balanced-match@^1.0.0: version "1.0.2" @@ -2156,18 +2377,10 @@ base64-js@^1.0.2, base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" +baseline-browser-mapping@^2.9.0: + version "2.9.18" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz#c8281693035a9261b10d662a5379650a6c2d1ff7" + integrity sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA== basic-auth@~2.0.1: version "2.0.1" @@ -2248,43 +2461,37 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.2, braces@~3.0.2: +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browserslist@^4.20.2: - version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== +browserslist@^4.24.0: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" - node-releases "^2.0.6" - update-browserslist-db "^1.0.5" + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" bs-logger@0.x: version "0.2.6" @@ -2305,6 +2512,11 @@ bson@*, bson@^1.1.4, bson@^6.10.4, bson@^6.7.0, bson@^7.0.0: resolved "https://registry.yarnpkg.com/bson/-/bson-6.10.4.tgz#d530733bb5bb16fb25c162e01a3344fab332fd2b" integrity sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng== +btoa-lite@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" + integrity sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA== + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -2315,7 +2527,7 @@ buffer-equal-constant-time@^1.0.1: resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== -buffer-from@1.x, buffer-from@^1.0.0: +buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== @@ -2354,21 +2566,6 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind-apply-helpers@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz#32e5892e6361b29b0b545ba6f7763378daca2840" @@ -2393,32 +2590,25 @@ call-bound@^1.0.2: call-bind-apply-helpers "^1.0.1" get-intrinsic "^1.2.6" -callsites@^3.0.0: +callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^5.0.0, camelcase@^5.3.1: +camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.3.0: +camelcase@^6.1.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001370: - version "1.0.30001373" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz#2dc3bc3bfcb5d5a929bec11300883040d7b4b4be" - integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ== - -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" +caniuse-lite@^1.0.30001759: + version "1.0.30001766" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz#b6f6b55cb25a2d888d9393104d14751c6a7d6f7a" + integrity sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA== chalk@^2.0.0, chalk@^2.1.0: version "2.4.2" @@ -2462,25 +2652,20 @@ chokidar@^3.5.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== +ci-info@^4.2.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa" + integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" +cjs-module-lexer@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca" + integrity sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ== cli-cursor@^3.1.0: version "3.1.0" @@ -2504,14 +2689,14 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" cloudpayments@^6.0.1: version "6.0.1" @@ -2544,18 +2729,10 @@ co@^4.6.0: axios "^0.20.0" typescript "^3.7.5" -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" +collect-v8-coverage@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80" + integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw== color-convert@^1.9.0: version "1.9.3" @@ -2608,11 +2785,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2635,12 +2807,10 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== cookie-signature@1.0.6: version "1.0.6" @@ -2652,11 +2822,6 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -2683,7 +2848,7 @@ cross-env@^10.0.0: "@epic-web/invariant" "^1.0.0" cross-spawn "^7.0.6" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -2694,16 +2859,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.6: +cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -2717,32 +2873,6 @@ cssfilter@0.0.10: resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - dataloader@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.1.0.tgz#c69c538235e85e7ac6c6c444bae8ecabf5de9df7" @@ -2753,7 +2883,7 @@ date-fns@2.15.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f" integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9, debug@~2.6.9: +debug@2.6.9, debug@^2.6.9, debug@~2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2788,37 +2918,27 @@ debug@^4.2.0: dependencies: ms "^2.1.3" -debug@^4.3.4, debug@^4.4.0, debug@^4.4.3: +debug@^4.3.1, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - -decimal.js@^10.2.1: - version "10.3.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" - integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== +dedent@^1.6.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.1.tgz#364661eea3d73f3faba7089214420ec2f8f13e15" + integrity sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg== deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" @@ -2828,28 +2948,6 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2870,12 +2968,17 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +deprecation@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -detect-newline@^3.0.0: +detect-newline@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== @@ -2887,10 +2990,10 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" -diff-sequences@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" - integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== diff@^4.0.1: version "4.0.2" @@ -2919,13 +3022,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" - dotenv@^16.0.1: version "16.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" @@ -2947,6 +3043,11 @@ dynamic-dedupe@^0.3.0: dependencies: xtend "^4.0.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" @@ -2959,15 +3060,15 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.202: - version "1.4.206" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.206.tgz#580ff85b54d7ec0c05f20b1e37ea0becdd7b0ee4" - integrity sha512-h+Fadt1gIaQ06JaIiyqPsBjJ08fV5Q7md+V8bUvQW/9OvXfL2LRICTz2EcnnCP7QzrFTS6/27MRV6Bl9Yn97zA== +electron-to-chromium@^1.5.263: + version "1.5.278" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz#807a5e321f012a41bfd64e653f35993c9af95493" + integrity sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw== -emittery@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" - integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^7.0.1: version "7.0.3" @@ -2979,12 +3080,17 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -3065,6 +3171,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -3080,18 +3191,6 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - eslint-config-codex@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/eslint-config-codex/-/eslint-config-codex-1.2.4.tgz#e622be4b27f8bc0b22b7e049830accdbfba0c1c3" @@ -3272,7 +3371,7 @@ espree@^6.1.2: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.1.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -3328,68 +3427,37 @@ eventsource-parser@^3.0.6: resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== +execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" is-stream "^2.0.0" merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expect@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" - integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== - dependencies: - "@jest/types" "^26.6.2" - ansi-styles "^4.0.0" - jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" +exit-x@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/exit-x/-/exit-x-0.2.2.tgz#1f9052de3b8d99a696b10dad5bced9bdd5c3aa64" + integrity sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ== + +expect@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-30.2.0.tgz#d4013bed267013c14bc1199cec8aa57cee9b5869" + integrity sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw== + dependencies: + "@jest/expect-utils" "30.2.0" + "@jest/get-type" "30.1.0" + jest-matcher-utils "30.2.0" + jest-message-util "30.2.0" + jest-mock "30.2.0" + jest-util "30.2.0" express@^4.17.1: version "4.18.1" @@ -3428,21 +3496,6 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -3452,20 +3505,6 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - fast-content-type-parse@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz#5590b6c807cc598be125e6740a9fde589d2b7afb" @@ -3491,10 +3530,10 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== +fb-watchman@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" @@ -3519,16 +3558,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -3536,6 +3565,13 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + finalhandler@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" @@ -3619,19 +3655,13 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== +foreground-child@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" + cross-spawn "^7.0.6" + signal-exit "^4.0.1" form-data@^4.0.0: version "4.0.0" @@ -3647,13 +3677,6 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -3684,7 +3707,12 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.1.2, fsevents@~2.3.2: +fsevents@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -3729,7 +3757,7 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== -get-caller-file@^2.0.1: +get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -3777,19 +3805,10 @@ get-proto@^1.0.0: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== get-symbol-description@^1.0.0: version "1.0.0" @@ -3799,11 +3818,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - glob-parent@^5.0.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3811,7 +3825,19 @@ glob-parent@^5.0.0, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^10.3.10: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3823,11 +3849,6 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - globals@^12.1.0: version "12.4.0" resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" @@ -3840,11 +3861,16 @@ gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== +graceful-fs@^4.2.11, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + graphql-scalars@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/graphql-scalars/-/graphql-scalars-1.17.0.tgz#10e4f0fe44472d475dd72f14412c065fd1b7aff8" @@ -3879,11 +3905,6 @@ graphql-upload@^13: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw== - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -3923,37 +3944,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -3973,13 +3963,6 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== - dependencies: - whatwg-encoding "^1.0.5" - html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -4007,15 +3990,6 @@ http-errors@^1.8.1: statuses ">= 1.5.0 < 2" toidentifier "1.0.1" -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -4032,10 +4006,10 @@ https-proxy-agent@^7.0.6: agent-base "^7.1.2" debug "4" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" @@ -4077,10 +4051,10 @@ import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" - integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== +import-local@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== dependencies: pkg-dir "^4.2.0" resolve-cwd "^3.0.0" @@ -4136,20 +4110,6 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arguments@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -4185,23 +4145,11 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" @@ -4209,20 +4157,6 @@ is-core-module@^2.8.1, is-core-module@^2.9.0: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -4230,41 +4164,6 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4280,7 +4179,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-generator-fn@^2.0.0: +is-generator-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== @@ -4311,30 +4210,11 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -4350,11 +4230,6 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -4385,11 +4260,6 @@ is-typed-array@^1.1.3, is-typed-array@^1.1.9: for-each "^0.3.3" has-tostringtag "^1.0.0" -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -4397,24 +4267,12 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== @@ -4424,43 +4282,21 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== - dependencies: - "@babel/core" "^7.7.5" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-instrument@^5.0.4: - version "5.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" - integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== dependencies: - "@babel/core" "^7.12.3" - "@babel/parser" "^7.14.7" - "@istanbuljs/schema" "^0.1.2" + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" istanbul-lib-coverage "^3.2.0" - semver "^6.3.0" + semver "^7.5.4" istanbul-lib-report@^3.0.0: version "3.0.0" @@ -4471,400 +4307,424 @@ istanbul-lib-report@^3.0.0: make-dir "^3.0.0" supports-color "^7.1.0" -istanbul-lib-source-maps@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" - integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== +istanbul-lib-source-maps@^5.0.0: + version "5.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== dependencies: + "@jridgewell/trace-mapping" "^0.3.23" debug "^4.1.1" istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" -istanbul-reports@^3.0.2: - version "3.1.5" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== +istanbul-reports@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== +jest-changed-files@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-30.2.0.tgz#602266e478ed554e1e1469944faa7efd37cee61c" + integrity sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ== dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" - prompts "^2.0.1" - yargs "^15.4.1" - -jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" - chalk "^4.0.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" - -jest-diff@^26.0.0, jest-diff@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" - integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + execa "^5.1.1" + jest-util "30.2.0" + p-limit "^3.1.0" + +jest-circus@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-30.2.0.tgz#98b8198b958748a2f322354311023d1d02e7603f" + integrity sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg== dependencies: - chalk "^4.0.0" - diff-sequences "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + "@jest/environment" "30.2.0" + "@jest/expect" "30.2.0" + "@jest/test-result" "30.2.0" + "@jest/types" "30.2.0" + "@types/node" "*" + chalk "^4.1.2" + co "^4.6.0" + dedent "^1.6.0" + is-generator-fn "^2.1.0" + jest-each "30.2.0" + jest-matcher-utils "30.2.0" + jest-message-util "30.2.0" + jest-runtime "30.2.0" + jest-snapshot "30.2.0" + jest-util "30.2.0" + p-limit "^3.1.0" + pretty-format "30.2.0" + pure-rand "^7.0.0" + slash "^3.0.0" + stack-utils "^2.0.6" + +jest-cli@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-30.2.0.tgz#1780f8e9d66bf84a10b369aea60aeda7697dcc67" + integrity sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA== + dependencies: + "@jest/core" "30.2.0" + "@jest/test-result" "30.2.0" + "@jest/types" "30.2.0" + chalk "^4.1.2" + exit-x "^0.2.2" + import-local "^3.2.0" + jest-config "30.2.0" + jest-util "30.2.0" + jest-validate "30.2.0" + yargs "^17.7.2" + +jest-config@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-30.2.0.tgz#29df8c50e2ad801cc59c406b50176c18c362a90b" + integrity sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA== + dependencies: + "@babel/core" "^7.27.4" + "@jest/get-type" "30.1.0" + "@jest/pattern" "30.0.1" + "@jest/test-sequencer" "30.2.0" + "@jest/types" "30.2.0" + babel-jest "30.2.0" + chalk "^4.1.2" + ci-info "^4.2.0" + deepmerge "^4.3.1" + glob "^10.3.10" + graceful-fs "^4.2.11" + jest-circus "30.2.0" + jest-docblock "30.2.0" + jest-environment-node "30.2.0" + jest-regex-util "30.0.1" + jest-resolve "30.2.0" + jest-runner "30.2.0" + jest-util "30.2.0" + jest-validate "30.2.0" + micromatch "^4.0.8" + parse-json "^5.2.0" + pretty-format "30.2.0" + slash "^3.0.0" + strip-json-comments "^3.1.1" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== +jest-diff@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825" + integrity sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A== dependencies: - detect-newline "^3.0.0" + "@jest/diff-sequences" "30.0.1" + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + pretty-format "30.2.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== dependencies: - "@jest/types" "^26.6.2" chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" - -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" - -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-30.2.0.tgz#42cd98d69f887e531c7352309542b1ce4ee10256" + integrity sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA== + dependencies: + detect-newline "^3.1.0" + +jest-each@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-30.2.0.tgz#39e623ae71641c2ac3ee69b3ba3d258fce8e768d" + integrity sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ== + dependencies: + "@jest/get-type" "30.1.0" + "@jest/types" "30.2.0" + chalk "^4.1.2" + jest-util "30.2.0" + pretty-format "30.2.0" + +jest-environment-node@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-30.2.0.tgz#3def7980ebd2fd86e74efd4d2e681f55ab38da0f" + integrity sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA== + dependencies: + "@jest/environment" "30.2.0" + "@jest/fake-timers" "30.2.0" + "@jest/types" "30.2.0" "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" + jest-mock "30.2.0" + jest-util "30.2.0" + jest-validate "30.2.0" -jest-get-type@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" - integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== -jest-haste-map@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== +jest-haste-map@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-30.2.0.tgz#808e3889f288603ac70ff0ac047598345a66022e" + integrity sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw== dependencies: - "@jest/types" "^26.6.2" - "@types/graceful-fs" "^4.1.2" + "@jest/types" "30.2.0" "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" + anymatch "^3.1.3" + fb-watchman "^2.0.2" + graceful-fs "^4.2.11" + jest-regex-util "30.0.1" + jest-util "30.2.0" + jest-worker "30.2.0" + micromatch "^4.0.8" + walker "^1.0.8" optionalDependencies: - fsevents "^2.1.2" - -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^26.6.2" - is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" - -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== - dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-matcher-utils@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" - integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== + fsevents "^2.3.3" + +jest-leak-detector@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz#292fdca7b7c9cf594e1e570ace140b01d8beb736" + integrity sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ== dependencies: - chalk "^4.0.0" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" + "@jest/get-type" "30.1.0" + pretty-format "30.2.0" -jest-message-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" - integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== +jest-matcher-utils@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz#69a0d4c271066559ec8b0d8174829adc3f23a783" + integrity sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg== + dependencies: + "@jest/get-type" "30.1.0" + chalk "^4.1.2" + jest-diff "30.2.0" + pretty-format "30.2.0" + +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - pretty-format "^26.6.2" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-30.2.0.tgz#fc97bf90d11f118b31e6131e2b67fc4f39f92152" + integrity sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@jest/types" "30.2.0" + "@types/stack-utils" "^2.0.3" + chalk "^4.1.2" + graceful-fs "^4.2.11" + micromatch "^4.0.8" + pretty-format "30.2.0" slash "^3.0.0" - stack-utils "^2.0.2" + stack-utils "^2.0.6" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== +jest-mock@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" + integrity sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "30.2.0" "@types/node" "*" + jest-util "30.2.0" -jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-pnp-resolver@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== jest-regex-util@30.0.1: version "30.0.1" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz#f17c1de3958b67dfe485354f5a10093298f2a49b" integrity sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA== -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== - -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== +jest-resolve-dependencies@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz#3370e2c0b49cc560f6a7e8ec3a59dd99525e1a55" + integrity sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w== dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" + jest-regex-util "30.0.1" + jest-snapshot "30.2.0" -jest-resolve@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" - integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== +jest-resolve@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-30.2.0.tgz#2e2009cbd61e8f1f003355d5ec87225412cebcd7" + integrity sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A== dependencies: - "@jest/types" "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.2" - jest-util "^26.6.2" - read-pkg-up "^7.0.1" - resolve "^1.18.1" + chalk "^4.1.2" + graceful-fs "^4.2.11" + jest-haste-map "30.2.0" + jest-pnp-resolver "^1.2.3" + jest-util "30.2.0" + jest-validate "30.2.0" slash "^3.0.0" + unrs-resolver "^1.7.11" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== +jest-runner@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-30.2.0.tgz#c62b4c3130afa661789705e13a07bdbcec26a114" + integrity sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ== dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/console" "30.2.0" + "@jest/environment" "30.2.0" + "@jest/test-result" "30.2.0" + "@jest/transform" "30.2.0" + "@jest/types" "30.2.0" "@types/node" "*" - chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" - source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - cjs-module-lexer "^0.6.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" + chalk "^4.1.2" + emittery "^0.13.1" + exit-x "^0.2.2" + graceful-fs "^4.2.11" + jest-docblock "30.2.0" + jest-environment-node "30.2.0" + jest-haste-map "30.2.0" + jest-leak-detector "30.2.0" + jest-message-util "30.2.0" + jest-resolve "30.2.0" + jest-runtime "30.2.0" + jest-util "30.2.0" + jest-watcher "30.2.0" + jest-worker "30.2.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-30.2.0.tgz#395ea792cde048db1b0cd1a92dc9cb9f1921bf8a" + integrity sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg== + dependencies: + "@jest/environment" "30.2.0" + "@jest/fake-timers" "30.2.0" + "@jest/globals" "30.2.0" + "@jest/source-map" "30.0.1" + "@jest/test-result" "30.2.0" + "@jest/transform" "30.2.0" + "@jest/types" "30.2.0" + "@types/node" "*" + chalk "^4.1.2" + cjs-module-lexer "^2.1.0" + collect-v8-coverage "^1.0.2" + glob "^10.3.10" + graceful-fs "^4.2.11" + jest-haste-map "30.2.0" + jest-message-util "30.2.0" + jest-mock "30.2.0" + jest-regex-util "30.0.1" + jest-resolve "30.2.0" + jest-snapshot "30.2.0" + jest-util "30.2.0" slash "^3.0.0" strip-bom "^4.0.0" - yargs "^15.4.1" -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== +jest-snapshot@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-30.2.0.tgz#266fbbb4b95fc4665ce6f32f1f38eeb39f4e26d0" + integrity sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA== + dependencies: + "@babel/core" "^7.27.4" + "@babel/generator" "^7.27.5" + "@babel/plugin-syntax-jsx" "^7.27.1" + "@babel/plugin-syntax-typescript" "^7.27.1" + "@babel/types" "^7.27.3" + "@jest/expect-utils" "30.2.0" + "@jest/get-type" "30.1.0" + "@jest/snapshot-utils" "30.2.0" + "@jest/transform" "30.2.0" + "@jest/types" "30.2.0" + babel-preset-current-node-syntax "^1.2.0" + chalk "^4.1.2" + expect "30.2.0" + graceful-fs "^4.2.11" + jest-diff "30.2.0" + jest-matcher-utils "30.2.0" + jest-message-util "30.2.0" + jest-util "30.2.0" + pretty-format "30.2.0" + semver "^7.7.2" + synckit "^0.11.8" + +jest-util@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-30.2.0.tgz#5142adbcad6f4e53c2776c067a4db3c14f913705" + integrity sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA== dependencies: + "@jest/types" "30.2.0" "@types/node" "*" - graceful-fs "^4.2.4" - -jest-snapshot@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" - integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.0.0" - chalk "^4.0.0" - expect "^26.6.2" - graceful-fs "^4.2.4" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - jest-haste-map "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - natural-compare "^1.4.0" - pretty-format "^26.6.2" - semver "^7.3.2" + chalk "^4.1.2" + ci-info "^4.2.0" + graceful-fs "^4.2.11" + picomatch "^4.0.2" -jest-util@^26.1.0, jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== +jest-util@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== dependencies: - "@jest/types" "^26.6.2" + "@jest/types" "^27.5.1" "@types/node" "*" chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== +jest-validate@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-30.2.0.tgz#273eaaed4c0963b934b5b31e96289edda6e0a2ef" + integrity sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw== dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" - chalk "^4.0.0" - jest-get-type "^26.3.0" + "@jest/get-type" "30.1.0" + "@jest/types" "30.2.0" + camelcase "^6.3.0" + chalk "^4.1.2" leven "^3.1.0" - pretty-format "^26.6.2" + pretty-format "30.2.0" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== +jest-watcher@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-30.2.0.tgz#f9c055de48e18c979e7756a3917e596e2d69b07b" + integrity sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg== dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" + "@jest/test-result" "30.2.0" + "@jest/types" "30.2.0" "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - jest-util "^26.6.2" - string-length "^4.0.1" + ansi-escapes "^4.3.2" + chalk "^4.1.2" + emittery "^0.13.1" + jest-util "30.2.0" + string-length "^4.0.2" -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== +jest-worker@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-30.2.0.tgz#fd5c2a36ff6058ec8f74366ec89538cc99539d26" + integrity sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g== dependencies: "@types/node" "*" + "@ungap/structured-clone" "^1.3.0" + jest-util "30.2.0" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.1.1" -jest@^26.2.2: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== +jest@^30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-30.2.0.tgz#9f0a71e734af968f26952b5ae4b724af82681630" + integrity sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A== dependencies: - "@jest/core" "^26.6.3" - import-local "^3.0.2" - jest-cli "^26.6.3" + "@jest/core" "30.2.0" + "@jest/types" "30.2.0" + import-local "^3.2.0" + jest-cli "30.2.0" jmespath@0.16.0: version "0.16.0" @@ -4884,43 +4744,10 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -jsdom@^16.4.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== json-parse-even-better-errors@^2.3.0: version "2.3.1" @@ -4942,7 +4769,7 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@2.x, json5@^2.2.1: +json5@2.x: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -4954,6 +4781,11 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonc-parser@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" @@ -5001,35 +4833,6 @@ jws@^4.0.1: jwa "^2.0.1" safe-buffer "^5.0.1" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -5108,6 +4911,11 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.mergewith@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" @@ -5128,7 +4936,7 @@ lodash@4.17.19: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== -lodash@4.x, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.7.0: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5143,6 +4951,18 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -5174,18 +4994,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" @@ -5221,31 +5029,12 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" picomatch "^2.3.1" migrate-mongo@^7.0.1: @@ -5291,23 +5080,22 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6: +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@1.x, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== mkdirp@^0.5.1: version "0.5.6" @@ -5316,6 +5104,11 @@ mkdirp@^0.5.1: dependencies: minimist "^1.2.6" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mongodb-connection-string-url@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz#e223089dfa0a5fa9bf505f8aedcbc67b077b33e7" @@ -5447,22 +5240,10 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" +napi-postinstall@^0.3.0: + version "0.3.4" + resolved "https://registry.yarnpkg.com/napi-postinstall/-/napi-postinstall-0.3.4.tgz#7af256d6588b5f8e952b9190965d6b019653bbb9" + integrity sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ== natural-compare@^1.4.0: version "1.4.0" @@ -5515,22 +5296,10 @@ node-int64@^0.4.0: resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-notifier@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" - integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== nodemon@^2.0.2: version "2.0.19" @@ -5555,7 +5324,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: +normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -5565,51 +5334,23 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== - dependencies: - remove-trailing-separator "^1.0.1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.0: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" -nwsapi@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" - integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== - object-assign@^4, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-hash@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" @@ -5635,13 +5376,6 @@ object-path@^0.11.8: resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.8.tgz#ed002c02bbdd0070b78a27455e8ae01fc14d4742" integrity sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -5652,13 +5386,6 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== - dependencies: - isobject "^3.0.1" - object.values@^1.1.0, object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -5687,14 +5414,14 @@ on-headers@~1.1.0: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -5708,7 +5435,7 @@ optional-require@^1.1.8: dependencies: require-at "^1.0.6" -optionator@^0.8.1, optionator@^0.8.3: +optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== @@ -5730,16 +5457,6 @@ p-each-series@2.1.0: resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== -p-each-series@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" - integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -5754,6 +5471,13 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -5778,6 +5502,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -5792,7 +5521,7 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" -parse-json@^5.0.0: +parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -5802,21 +5531,11 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - parseurl@^1.3.3, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -5832,7 +5551,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^2.0.0, path-key@^2.0.1: +path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== @@ -5847,6 +5566,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -5864,25 +5591,30 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== -pirates@^4.0.1: - version "4.0.5" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== +pirates@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22" + integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" @@ -5891,24 +5623,27 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== -pretty-format@^26.0.0, pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== +pretty-format@30.2.0: + version "30.2.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.2.0.tgz#2d44fe6134529aed18506f6d11509d8a62775ebe" + integrity sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA== dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" + "@jest/schemas" "30.0.5" + ansi-styles "^5.2.0" + react-is "^18.3.1" + +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" react-is "^17.0.1" process-nextick-args@~2.0.0: @@ -5934,14 +5669,6 @@ promise-breaker@^5.0.0: resolved "https://registry.yarnpkg.com/promise-breaker/-/promise-breaker-5.0.0.tgz#58e8541f1619554057da95a211794d7834d30c1d" integrity sha512-mgsWQuG4kJ1dtO6e/QlNDLFtMkMzzecsC69aI5hlLEjGHFNpHrvGhFi4LiK5jg2SMQj74/diH+wZliL9LpGsyA== -prompts@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -5950,30 +5677,17 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== - pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -5983,6 +5697,11 @@ punycode@^2.3.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +pure-rand@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-7.0.1.tgz#6f53a5a9e3e4a47445822af96821ca509ed37566" + integrity sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ== + qs@6.10.3: version "6.10.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" @@ -6027,6 +5746,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + read-pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" @@ -6035,15 +5759,6 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - read-pkg@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" @@ -6053,16 +5768,6 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - "readable-stream@1.x >=1.1.9": version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -6119,14 +5824,6 @@ redis@^4.7.0: "@redis/search" "1.2.0" "@redis/time-series" "1.1.0" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexp-tree@~0.1.1: version "0.1.27" resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" @@ -6151,21 +5848,6 @@ regexpp@^3.0.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - require-at@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/require-at/-/require-at-1.0.6.tgz#9eb7e3c5e00727f5a4744070a7f560d4de4f6e6a" @@ -6176,11 +5858,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - require_optional@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" @@ -6216,12 +5893,7 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - -resolve@^1.0.0, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.18.1, resolve@^1.20.0, resolve@^1.22.0: +resolve@^1.0.0, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.20.0, resolve@^1.22.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -6238,11 +5910,6 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - retry@0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -6262,18 +5929,6 @@ rimraf@^2.6.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -6296,13 +5951,6 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, s resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - safe-regex@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" @@ -6315,21 +5963,6 @@ safe-regex@^2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - saslprep@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" @@ -6347,13 +5980,6 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== - dependencies: - xmlchars "^2.2.0" - "semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.5.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -6366,12 +5992,17 @@ semver@7.x, semver@^7.3.2, semver@^7.3.5: dependencies: lru-cache "^6.0.0" -semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.1.2: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.5.4, semver@^7.7.3: +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.4, semver@^7.7.2, semver@^7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== @@ -6410,21 +6041,6 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -6462,11 +6078,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - side-channel-list@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" @@ -6516,11 +6127,16 @@ side-channel@^1.0.6: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-update-notifier@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" @@ -6528,11 +6144,6 @@ simple-update-notifier@^1.0.7: dependencies: semver "~7.0.0" -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -6547,48 +6158,15 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: - kind-of "^3.2.0" + buffer-from "^1.0.0" + source-map "^0.6.0" -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.5.12, source-map-support@^0.5.6: +source-map-support@^0.5.12: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -6596,26 +6174,11 @@ source-map-support@^0.5.12, source-map-support@^0.5.6: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - sparse-bitfield@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" @@ -6649,13 +6212,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6666,21 +6222,13 @@ stack-trace@^0.0.10: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== -stack-utils@^2.0.2: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== +stack-utils@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -6705,7 +6253,7 @@ streamx@^2.15.0: fast-fifo "^1.3.2" text-decoder "^1.1.0" -string-length@^4.0.1: +string-length@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== @@ -6713,6 +6261,15 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -6722,7 +6279,7 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6731,6 +6288,15 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.trimend@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" @@ -6768,6 +6334,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -6782,6 +6355,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" + integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -6792,11 +6372,6 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -6807,7 +6382,7 @@ strip-json-comments@^2.0.0: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strip-json-comments@^3.0.1: +strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -6819,30 +6394,31 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" - supports-color "^7.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +synckit@^0.11.8: + version "0.11.12" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.12.tgz#abe74124264fbc00a48011b0d98bdc1cffb64a7b" + integrity sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ== + dependencies: + "@pkgr/core" "^0.2.9" table@^5.2.3: version "5.4.6" @@ -6881,14 +6457,6 @@ tdigest@^0.1.1: dependencies: bintrees "1.0.2" -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -6910,11 +6478,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== - through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -6942,21 +6505,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -6964,16 +6512,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -6986,22 +6524,6 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.1.2" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - tr46@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.1.1.tgz#96ae867cddb8fdb64a49cc3059a8d428bcf238ca" @@ -7019,19 +6541,17 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -ts-jest@^26.1.4: - version "26.5.6" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35" - integrity sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA== +ts-jest@^27.1.0: + version "27.1.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" + integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== dependencies: bs-logger "0.x" - buffer-from "1.x" fast-json-stable-stringify "2.x" - jest-util "^26.1.0" + jest-util "^27.0.0" json5 "2.x" - lodash "4.x" + lodash.memoize "4.x" make-error "1.x" - mkdirp "1.x" semver "7.x" yargs-parser "20.x" @@ -7134,11 +6654,6 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -7152,13 +6667,6 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typescript@^3.7.5: version "3.9.10" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" @@ -7184,26 +6692,16 @@ undefsafe@^2.0.5: resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" +universal-user-agent@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa" + integrity sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ== universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz#c05870a58125a2dc00431f2df815a77fe69736be" integrity sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A== -universalify@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - universalify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" @@ -7219,21 +6717,40 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== +unrs-resolver@^1.7.11: + version "1.11.1" + resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz#be9cd8686c99ef53ecb96df2a473c64d304048a9" + integrity sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg== dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -update-browserslist-db@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" - integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== + napi-postinstall "^0.3.0" + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi" "1.11.1" + "@unrs/resolver-binding-android-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-arm64" "1.11.1" + "@unrs/resolver-binding-darwin-x64" "1.11.1" + "@unrs/resolver-binding-freebsd-x64" "1.11.1" + "@unrs/resolver-binding-linux-arm-gnueabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm-musleabihf" "1.11.1" + "@unrs/resolver-binding-linux-arm64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-arm64-musl" "1.11.1" + "@unrs/resolver-binding-linux-ppc64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-riscv64-musl" "1.11.1" + "@unrs/resolver-binding-linux-s390x-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-gnu" "1.11.1" + "@unrs/resolver-binding-linux-x64-musl" "1.11.1" + "@unrs/resolver-binding-wasm32-wasi" "1.11.1" + "@unrs/resolver-binding-win32-arm64-msvc" "1.11.1" + "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" + "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" + +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.2.0" + picocolors "^1.1.1" uri-js@^4.2.2: version "4.4.1" @@ -7242,11 +6759,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - url-parse@~1.4.3: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" @@ -7263,11 +6775,6 @@ url@0.10.3: punycode "1.3.2" querystring "0.2.0" -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -7295,7 +6802,7 @@ uuid@8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== -uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.1, uuid@^8.3.2: +uuid@^8.0.0, uuid@^8.3.1, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -7310,14 +6817,14 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -v8-to-istanbul@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" - integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: + "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" + convert-source-map "^2.0.0" validate-npm-package-license@^3.0.1: version "3.0.4" @@ -7337,21 +6844,7 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" - -walker@^1.0.7, walker@~1.0.5: +walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -7363,33 +6856,11 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - whatwg-mimetype@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" @@ -7411,15 +6882,6 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -7431,11 +6893,6 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== - which-typed-array@^1.1.2: version "1.1.8" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.8.tgz#0cfd53401a6f334d90ed1125754a42ed663eb01f" @@ -7455,7 +6912,7 @@ which@^1.2.9: dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -7467,29 +6924,45 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== +write-file-atomic@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7" + integrity sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw== dependencies: imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" + signal-exit "^4.0.1" write@1.0.3: version "1.0.3" @@ -7498,11 +6971,6 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -ws@^7.4.6: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== - xml-crypto@^6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-6.1.2.tgz#ed93e87d9538f92ad1ad2db442e9ec586723d07d" @@ -7521,11 +6989,6 @@ xml-encryption@^3.1.0: escape-html "^1.0.3" xpath "0.0.32" -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" @@ -7557,11 +7020,6 @@ xmlbuilder@~9.0.1: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" integrity sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ== -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - xpath@0.0.32: version "0.0.32" resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" @@ -7590,45 +7048,43 @@ xtend@^4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@4.0.0, yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yargs-parser@20.x: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^15.4.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== +yargs@^17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" yauzl@^2.10.0: version "2.10.0" @@ -7651,6 +7107,11 @@ yn@3.1.1: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + zod@^3.25.76: version "3.25.76" resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" From 5cddb5b996dc49795b1ca0f0ba558542660144d2 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 15:40:29 +0300 Subject: [PATCH 08/15] Refactor Copilot assignment to use GraphQL and OAuth Updated the assignCopilot method to use the user-to-server OAuth token and GitHub GraphQL API for assigning the Copilot agent to issues. Improved error handling, added detailed logging, and ensured compatibility with the Copilot bot assignment process. Also added input validation for installationId in relevant methods. --- src/integrations/github/service.ts | 230 +++++++++++++++++++++++++---- 1 file changed, 202 insertions(+), 28 deletions(-) diff --git a/src/integrations/github/service.ts b/src/integrations/github/service.ts index ca843c5f..d7377664 100644 --- a/src/integrations/github/service.ts +++ b/src/integrations/github/service.ts @@ -280,6 +280,10 @@ export class GitHubService { /** * Get installation access token */ + if (!installationId) { + throw new Error('installationId is required for getting repositories'); + } + const accessToken = await this.createInstallationToken(installationId); /** @@ -351,17 +355,17 @@ export class GitHubService { } /** - * Create a GitHub issue + * Create a GitHub issue using GitHub App installation token * * @param {string} repoFullName - Repository full name (owner/repo) - * @param {string} installationId - GitHub App installation ID + * @param {string | null} installationId - GitHub App installation ID * @param {IssueData} issueData - Issue data (title, body, labels) * @returns {Promise} Created issue * @throws {Error} If issue creation fails */ public async createIssue( repoFullName: string, - installationId: string, + installationId: string | null, issueData: IssueData ): Promise { const [owner, repo] = repoFullName.split('/'); @@ -371,15 +375,22 @@ export class GitHubService { } /** - * Get installation access token + * Get installation access token (GitHub App token) */ + if (!installationId) { + throw new Error('installationId is required for creating GitHub issues'); + } + const accessToken = await this.createInstallationToken(installationId); /** - * Create Octokit instance with installation access token and configured timeout + * Create Octokit instance with installation token and configured timeout */ const octokit = this.createOctokit(accessToken); + /** + * Create issue via REST API using installation token + */ try { const { data } = await octokit.rest.issues.create({ owner, @@ -401,44 +412,207 @@ export class GitHubService { } /** - * Assign GitHub Copilot to an issue + * Assign Copilot agent to a GitHub issue using user-to-server OAuth token * - * @param {string} owner - Repository owner - * @param {string} repo - Repository name + * @param {string} repoFullName - Repository full name (owner/repo) * @param {number} issueNumber - Issue number - * @param {string} installationId - GitHub App installation ID - * @returns {Promise} True if assignment was successful - * @throws {Error} If assignment fails + * @param {string} delegatedUserToken - User-to-server OAuth token + * @returns {Promise} + * @throws {Error} If Copilot assignment fails */ public async assignCopilot( - owner: string, - repo: string, + repoFullName: string, issueNumber: number, - installationId: string - ): Promise { - /** - * Get installation access token - */ - const accessToken = await this.createInstallationToken(installationId); + delegatedUserToken: string + ): Promise { + const [owner, repo] = repoFullName.split('/'); + + if (!owner || !repo) { + throw new Error(`Invalid repository name format: ${repoFullName}. Expected format: owner/repo`); + } /** - * Create Octokit instance with installation access token and configured timeout + * Create Octokit instance with user-to-server OAuth token */ - const octokit = this.createOctokit(accessToken); + const octokit = this.createOctokit(delegatedUserToken); try { /** - * Assign GitHub Copilot coding agent (copilot-swe-agent[bot]) as assignee - * According to GitHub docs: https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/create-a-pr + * Step 1: Get repository ID and find Copilot bot ID */ - await octokit.rest.issues.addAssignees({ + const repoInfoQuery = ` + query($owner: String!, $name: String!) { + repository(owner: $owner, name: $name) { + id + issue(number: ${issueNumber}) { + id + } + suggestedActors(capabilities: [CAN_BE_ASSIGNED], first: 100) { + nodes { + login + __typename + ... on Bot { + id + } + ... on User { + id + } + } + } + } + } + `; + + const repoInfo: any = await octokit.graphql(repoInfoQuery, { owner, - repo, - issue_number: issueNumber, - assignees: ['copilot-swe-agent[bot]'], + name: repo, }); - return true; + console.log('[GitHub API] Repository info query response:', JSON.stringify(repoInfo, null, 2)); + + const repositoryId = repoInfo?.repository?.id; + const issueId = repoInfo?.repository?.issue?.id; + + if (!repositoryId) { + throw new Error(`Failed to get repository ID for ${repoFullName}`); + } + + if (!issueId) { + throw new Error(`Failed to get issue ID for issue #${issueNumber}`); + } + + /** + * Find Copilot bot in suggested actors + */ + let copilotBot = repoInfo.repository.suggestedActors.nodes.find( + (node: any) => node.login === 'copilot-swe-agent' + ); + + console.log('[GitHub API] Copilot bot found in suggestedActors:', copilotBot ? { login: copilotBot.login, id: copilotBot.id } : 'not found'); + + /** + * If not found in suggestedActors, try to get it directly by login + */ + if (!copilotBot || !copilotBot.id) { + console.log('[GitHub API] Trying to get Copilot bot directly by login...'); + + try { + const copilotBotQuery = ` + query($login: String!) { + user(login: $login) { + id + login + __typename + } + } + `; + + const copilotUserInfo: any = await octokit.graphql(copilotBotQuery, { + login: 'copilot-swe-agent', + }); + + console.log('[GitHub API] Direct Copilot bot query response:', JSON.stringify(copilotUserInfo, null, 2)); + + if (copilotUserInfo?.user?.id) { + copilotBot = { + login: copilotUserInfo.user.login, + id: copilotUserInfo.user.id, + }; + } + } catch (directQueryError) { + console.log('[GitHub API] Failed to get Copilot bot directly:', directQueryError); + } + } + + if (!copilotBot || !copilotBot.id) { + throw new Error('Copilot coding agent (copilot-swe-agent) is not available for this repository'); + } + + console.log('[GitHub API] Using Copilot bot:', { login: copilotBot.login, id: copilotBot.id }); + + /** + * Step 2: Assign Copilot to issue via GraphQL + * Note: Assignable is a union type (Issue | PullRequest), so we need to use fragments + */ + const assignCopilotMutation = ` + mutation($issueId: ID!, $assigneeIds: [ID!]!) { + addAssigneesToAssignable(input: { + assignableId: $issueId + assigneeIds: $assigneeIds + }) { + assignable { + ... on Issue { + id + number + assignees(first: 10) { + nodes { + login + } + } + } + ... on PullRequest { + id + number + assignees(first: 10) { + nodes { + login + } + } + } + } + } + } + `; + + const response: any = await octokit.graphql(assignCopilotMutation, { + issueId, + assigneeIds: [copilotBot.id], + }); + + console.log('[GitHub API] Assign Copilot mutation response:', JSON.stringify(response, null, 2)); + + const assignable = response?.addAssigneesToAssignable?.assignable; + + if (!assignable) { + throw new Error('Failed to assign Copilot to issue'); + } + + /** + * Assignable is a union type (Issue | PullRequest), so we need to check which type it is + * Both Issue and PullRequest have assignees field, so we can access it directly + * + * Note: The assignees list might not be immediately updated in the response, + * so we check if the mutation succeeded (assignable is not null) rather than + * verifying the assignees list directly + */ + const assignedLogins = assignable.assignees?.nodes?.map((n: any) => n.login) || []; + + /** + * Log assignees for debugging (but don't fail if Copilot is not in the list yet) + * GitHub API might not immediately reflect the assignment in the response + */ + console.log(`[GitHub API] Issue assignees after mutation:`, assignedLogins); + + /** + * Get issue number from assignable (works for both Issue and PullRequest) + */ + const assignedNumber = assignable.number; + + /** + * If Copilot is in the list, log success. Otherwise, just log a warning + * but don't throw an error, as the mutation might have succeeded even if + * the response doesn't show the assignee yet + */ + if (assignedLogins.includes('copilot-swe-agent')) { + console.log(`[GitHub API] Successfully assigned Copilot to issue #${assignedNumber}`); + } else { + /** + * Mutation succeeded (assignable is not null), but assignees list might not be updated yet + * This is a known behavior of GitHub API - the mutation succeeds but the response + * might not immediately reflect the new assignee + */ + console.log(`[GitHub API] Copilot assignment mutation completed for issue #${assignedNumber}, but assignees list not yet updated in response`); + } } catch (error) { throw new Error(`Failed to assign Copilot: ${error instanceof Error ? error.message : String(error)}`); } From 1c382e0f97133da28649c4c108e6f5cbfa2c4f3b Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 25 Jan 2026 12:43:39 +0000 Subject: [PATCH 09/15] Bump version up to 1.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe5280a2..09fe8712 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.3.1", + "version": "1.3.2", "main": "index.ts", "license": "BUSL-1.1", "scripts": { From 0473915d4e3242c4d786e55ecb0ce5d1ab938b7a Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 15:48:39 +0300 Subject: [PATCH 10/15] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09fe8712..7322424d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.3.2", + "version": "1.4.0", "main": "index.ts", "license": "BUSL-1.1", "scripts": { From d33289f806e094c1801355d4554d431ddfc2b62c Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 16:17:42 +0300 Subject: [PATCH 11/15] lint code --- src/integrations/github/routes.ts | 187 ++++--------------- src/integrations/github/service.ts | 285 +++++++++++++++++------------ src/sso/saml/controller.ts | 5 + 3 files changed, 205 insertions(+), 272 deletions(-) diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index 24b7997d..4c17dade 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -4,24 +4,26 @@ import { v4 as uuid } from 'uuid'; import { ObjectId } from 'mongodb'; import { createHmac } from 'crypto'; import { GitHubService } from './service'; +import { ProjectDBScheme } from '@hawk.so/types'; import { ContextFactories } from '../../types/graphql'; import { RedisInstallStateStore } from './store/install-state.redis.store'; +import ProjectModel from '../../models/project'; import WorkspaceModel from '../../models/workspace'; import { sgr, Effect } from '../../utils/ansi'; import { databases } from '../../mongo'; -/** - * Create GitHub router - * - * @param factories - context factories for database access - * @returns Express router with GitHub integration endpoints - */ /** * Default task threshold for automatic task creation * Minimum totalCount required to trigger auto-task creation */ const DEFAULT_TASK_THRESHOLD_TOTAL_COUNT = 50; +/** + * Create GitHub router + * + * @param factories - context factories for database access + * @returns Express router with GitHub integration endpoints + */ export function createGitHubRouter(factories: ContextFactories): express.Router { const router = express.Router(); const githubService = new GitHubService(); @@ -61,8 +63,8 @@ export function createGitHubRouter(factories: ContextFactories): express.Router req: express.Request, res: express.Response, projectId: string | undefined, - errorMessagePrefix: string = 'perform this action' - ): Promise<{ project: any; workspace: any; userId: string } | null> { + errorMessagePrefix = 'perform this action' + ): Promise<{ project: ProjectModel; workspace: WorkspaceModel; userId: string } | null> { const userId = req.context?.user?.id; /** @@ -143,14 +145,18 @@ export function createGitHubRouter(factories: ContextFactories): express.Router return null; } - return { project, workspace, userId }; + return { + project, + workspace, + userId, + }; } /** * Log message with GitHub Integration prefix * * @param level - log level ('log', 'warn', 'error', 'info') - * @param projectId - optional project ID to include in log prefix + * @param projectIdOrFirstArg - optional project ID to include in log prefix, or first log argument if not a valid ObjectId * @param args - arguments to log */ function log(level: 'log' | 'warn' | 'error' | 'info', projectIdOrFirstArg?: string | unknown, ...args: unknown[]): void { @@ -263,133 +269,6 @@ export function createGitHubRouter(factories: ContextFactories): express.Router } }); - /** - * GET /integration/github/callback?state=&installation_id= - * Handle GitHub App installation callback - * - * @deprecated - now we use /oauth endpoint for both installation and OAuth callbacks - */ - // router.get('/callback', async (req, res, next) => { - // try { - // const { state, installation_id } = req.query; - - // /** - // * Log callback request for debugging - // */ - // log('info', `Callback received: state=${state}, installation_id=${installation_id}, query=${JSON.stringify(req.query)}`); - - // /** - // * Validate required parameters - // */ - // if (!state || typeof state !== 'string') { - // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { - // error: 'Missing or invalid state', - // })); - // } - - // if (!installation_id || typeof installation_id !== 'string') { - // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { - // error: 'Missing or invalid installation_id parameter', - // })); - // } - - // /** - // * Verify state (CSRF protection) - // * getState() atomically gets and deletes the state, preventing reuse - // */ - // const stateData = await stateStore.getState(state); - - // if (!stateData) { - // log('warn', `Invalid or expired state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); - - // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { - // error: 'Invalid or expired state. Please try connecting again.', - // })); - // } - - // const { projectId, userId } = stateData; - - // log('info', projectId, `Processing callback initiated by user ${sgr(userId, Effect.ForegroundCyan)}`); - - // /** - // * Verify project exists - // */ - // const project = await factories.projectsFactory.findById(projectId); - - // if (!project) { - // log('error', projectId, 'Project not found'); - - // return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { - // error: `Project not found: ${projectId}`, - // })); - // } - - // /** - // * Get installation info from GitHub - // */ - // let installation; - - // try { - // installation = await githubService.getInstallationForRepository(installation_id); - // log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`); - // } catch (error) { - // log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`); - - // return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { - // error: 'Failed to retrieve GitHub installation information. Please try again.', - // })); - // } - - // /** - // * For now, we save only installationId - // * repoId and repoFullName will be set when creating the first issue or can be configured later - // * GitHub App installation can include multiple repositories, so we don't know which one to use yet - // */ - // const taskManagerConfig = { - // type: 'github', - // autoTaskEnabled: false, - // taskThresholdTotalCount: DEFAULT_TASK_THRESHOLD_TOTAL_COUNT, - // assignAgent: false, - // connectedAt: new Date(), - // updatedAt: new Date(), - // config: { - // installationId: installation_id, - // repoId: '', - // repoFullName: '', - // }, - // }; - - // let successRedirectUrl = buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { - // success: 'true', - // }); - - // /** - // * Save taskManager configuration to project - // */ - // try { - // await project.updateProject(({ - // taskManager: taskManagerConfig, - // }) as any); - - // log('info', projectId, 'Successfully connected GitHub integration. Redirecting to ' + sgr(successRedirectUrl, Effect.ForegroundGreen)); - // } catch (error) { - // log('error', projectId, `Failed to save taskManager config: ${error instanceof Error ? error.message : String(error)}`); - - // return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { - // error: 'Failed to save Task Manager configuration. Please try again.', - // })); - // } - - // /** - // * Redirect to Garage with success parameter - // */ - // return res.redirect(successRedirectUrl); - // } catch (error) { - // log('error', 'Error in /callback endpoint:', error); - // next(error); - // } - // }); - /** * GET /integration/github/oauth?code=&state=&installation_id= * Handle GitHub OAuth callback for user-to-server token @@ -397,11 +276,13 @@ export function createGitHubRouter(factories: ContextFactories): express.Router */ router.get('/oauth', async (req, res, next) => { try { + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase const { code, state, installation_id } = req.query; /** * Log OAuth callback request for debugging */ + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase log('info', `OAuth callback received: state=${state}, code=${code ? 'present' : 'missing'}, installation_id=${installation_id ? 'present' : 'missing'}, query=${JSON.stringify(req.query)}`); /** @@ -454,16 +335,16 @@ export function createGitHubRouter(factories: ContextFactories): express.Router * If installation_id is present, handle GitHub App installation first * This happens when "Request user authorization (OAuth) during installation" is enabled */ + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase if (installation_id && typeof installation_id === 'string') { + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase log('info', projectId, `GitHub App installation detected (installation_id: ${installation_id}), processing installation first`); /** - * Get installation info from GitHub + * Get installation info from GitHub (validates installation exists) */ - let installation; - try { - installation = await githubService.getInstallationForRepository(installation_id); + await githubService.getInstallationForRepository(installation_id); log('info', projectId, `Retrieved installation info for installation_id: ${sgr(installation_id, Effect.ForegroundCyan)}`); } catch (error) { log('error', projectId, `Failed to get installation info: ${error instanceof Error ? error.message : String(error)}`); @@ -484,6 +365,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router connectedAt: new Date(), updatedAt: new Date(), config: { + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installationId: installation_id, repoId: '', repoFullName: '', @@ -497,10 +379,11 @@ export function createGitHubRouter(factories: ContextFactories): express.Router ...taskManagerConfig, config: { ...project.taskManager.config, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installationId: installation_id, }, } : taskManagerConfig, - } as any); + } as Partial); log('info', projectId, 'Successfully saved GitHub App installation'); } catch (error) { @@ -592,7 +475,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router try { await project.updateProject({ taskManager: updatedTaskManager, - } as any); + } as Partial); log('info', projectId, `Successfully saved delegatedUser token for user ${sgr(tokenData.user.login, Effect.ForegroundCyan)}`); } catch (error) { @@ -654,7 +537,8 @@ export function createGitHubRouter(factories: ContextFactories): express.Router */ const payload = req.body as Buffer; const hmac = createHmac('sha256', webhookSecret); - hmac.update(payload as any); + + hmac.update(payload.toString('binary'), 'binary'); const calculatedSignature = `sha256=${hmac.digest('hex')}`; /** @@ -683,10 +567,12 @@ export function createGitHubRouter(factories: ContextFactories): express.Router /** * Parse webhook payload */ - let payloadData: any; + type GitHubWebhookPayload = { installation?: { id?: number | string }; action?: string }; + + let payloadData: GitHubWebhookPayload; try { - payloadData = JSON.parse(payload.toString()); + payloadData = JSON.parse(payload.toString()) as GitHubWebhookPayload; } catch (error) { log('error', 'Failed to parse webhook payload:', error); @@ -797,7 +683,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router /** * Check if taskManager is configured */ - const taskManager = (project as any).taskManager; + const taskManager = project.taskManager; if (!taskManager) { res.status(400).json({ error: 'Task Manager is not configured for this project' }); @@ -825,7 +711,8 @@ export function createGitHubRouter(factories: ContextFactories): express.Router /** * Log repository details for debugging */ - const repoOwners = [...new Set(repositories.map((r) => r.fullName.split('/')[0]))]; + const repoOwners = [ ...new Set(repositories.map((r) => r.fullName.split('/')[0])) ]; + log('info', projectId, `Retrieved ${repositories.length} repository(ies) for installation ${installationId}`); log('info', projectId, `Repository owners: ${repoOwners.join(', ')}`); @@ -884,7 +771,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router /** * Check if taskManager is configured */ - const taskManager = (project as any).taskManager; + const taskManager = project.taskManager; if (!taskManager) { res.status(400).json({ error: 'Task Manager is not configured for this project' }); @@ -908,7 +795,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router try { await project.updateProject({ taskManager: updatedTaskManager, - } as any); + }); log('info', validatedProjectId, `Updated repository selection: ${repoFullName} (${repoId})`); diff --git a/src/integrations/github/service.ts b/src/integrations/github/service.ts index d7377664..301dd8b4 100644 --- a/src/integrations/github/service.ts +++ b/src/integrations/github/service.ts @@ -84,6 +84,7 @@ export type Installation = { /** * Installation target type */ + // eslint-disable-next-line camelcase target_type: string; /** @@ -96,6 +97,12 @@ export type Installation = { * Service for interacting with GitHub API */ export class GitHubService { + /** + * Default timeout for GitHub API requests (in milliseconds) + * Increased from default 10s to 60s to handle slow network connections + */ + private static readonly DEFAULT_TIMEOUT = 10000; + /** * GitHub App ID from environment variables */ @@ -118,12 +125,6 @@ export class GitHubService { */ private readonly clientSecret: string; - /** - * Default timeout for GitHub API requests (in milliseconds) - * Increased from default 10s to 60s to handle slow network connections - */ - private static readonly DEFAULT_TIMEOUT = 60000; - /** * Creates an instance of GitHubService */ @@ -146,21 +147,6 @@ export class GitHubService { this.clientSecret = process.env.GITHUB_APP_CLIENT_SECRET; } - /** - * Create Octokit instance with configured timeout - * - * @param auth - Authentication token (JWT or installation access token) - * @returns Configured Octokit instance - */ - private createOctokit(auth: string): Octokit { - return new Octokit({ - auth, - request: { - timeout: GitHubService.DEFAULT_TIMEOUT, - }, - }); - } - /** * Generate URL for GitHub App installation * @@ -229,6 +215,7 @@ export class GitHubService { try { const { data } = await octokit.rest.apps.getInstallation({ + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installation_id: parseInt(installationId, 10), }); @@ -261,6 +248,7 @@ export class GitHubService { login: accountLogin, type: accountType, }, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase target_type: data.target_type, permissions: data.permissions || {}, }; @@ -300,6 +288,7 @@ export class GitHubService { const jwtOctokit = this.createOctokit(jwtToken); const installationInfo = await jwtOctokit.rest.apps.getInstallation({ + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installation_id: parseInt(installationId, 10), }); @@ -309,7 +298,9 @@ export class GitHubService { console.log('Installation info:', { id: installationInfo.data.id, account: installationInfo.data.account, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase target_type: installationInfo.data.target_type, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase repository_selection: installationInfo.data.repository_selection, }); @@ -322,7 +313,9 @@ export class GitHubService { const repositoriesData = await octokit.paginate( octokit.rest.apps.listReposAccessibleToInstallation, { + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installation_id: parseInt(installationId, 10), + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase per_page: 100, } ); @@ -402,6 +395,7 @@ export class GitHubService { return { number: data.number, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase html_url: data.html_url, title: data.title, state: data.state, @@ -463,7 +457,17 @@ export class GitHubService { } `; - const repoInfo: any = await octokit.graphql(repoInfoQuery, { + type RepoInfoGraphQLResponse = { + repository?: { + id: string; + issue?: { id: string }; + suggestedActors: { + nodes: Array<{ login: string; __typename?: string; id?: string }>; + }; + }; + }; + + const repoInfo = await octokit.graphql(repoInfoQuery, { owner, name: repo, }); @@ -484,11 +488,15 @@ export class GitHubService { /** * Find Copilot bot in suggested actors */ - let copilotBot = repoInfo.repository.suggestedActors.nodes.find( - (node: any) => node.login === 'copilot-swe-agent' + type SuggestedActorNode = { login: string; __typename?: string; id?: string }; + let copilotBot = (repoInfo?.repository?.suggestedActors?.nodes ?? []).find( + (node: SuggestedActorNode) => node.login === 'copilot-swe-agent' ); - console.log('[GitHub API] Copilot bot found in suggestedActors:', copilotBot ? { login: copilotBot.login, id: copilotBot.id } : 'not found'); + console.log('[GitHub API] Copilot bot found in suggestedActors:', copilotBot ? { + login: copilotBot.login, + id: copilotBot.id, + } : 'not found'); /** * If not found in suggestedActors, try to get it directly by login @@ -507,7 +515,11 @@ export class GitHubService { } `; - const copilotUserInfo: any = await octokit.graphql(copilotBotQuery, { + type CopilotUserInfoGraphQLResponse = { + user?: { id: string; login: string; __typename?: string }; + }; + + const copilotUserInfo = await octokit.graphql(copilotBotQuery, { login: 'copilot-swe-agent', }); @@ -528,7 +540,10 @@ export class GitHubService { throw new Error('Copilot coding agent (copilot-swe-agent) is not available for this repository'); } - console.log('[GitHub API] Using Copilot bot:', { login: copilotBot.login, id: copilotBot.id }); + console.log('[GitHub API] Using Copilot bot:', { + login: copilotBot.login, + id: copilotBot.id, + }); /** * Step 2: Assign Copilot to issue via GraphQL @@ -564,9 +579,19 @@ export class GitHubService { } `; - const response: any = await octokit.graphql(assignCopilotMutation, { + type AssignCopilotGraphQLResponse = { + addAssigneesToAssignable?: { + assignable?: { + id: string; + number: number; + assignees?: { nodes?: Array<{ login: string }> }; + }; + }; + }; + + const response = await octokit.graphql(assignCopilotMutation, { issueId, - assigneeIds: [copilotBot.id], + assigneeIds: [ copilotBot.id ], }); console.log('[GitHub API] Assign Copilot mutation response:', JSON.stringify(response, null, 2)); @@ -577,15 +602,16 @@ export class GitHubService { throw new Error('Failed to assign Copilot to issue'); } + // eslint-disable-next-line valid-jsdoc /** * Assignable is a union type (Issue | PullRequest), so we need to check which type it is * Both Issue and PullRequest have assignees field, so we can access it directly - * + * * Note: The assignees list might not be immediately updated in the response, * so we check if the mutation succeeded (assignable is not null) rather than * verifying the assignees list directly */ - const assignedLogins = assignable.assignees?.nodes?.map((n: any) => n.login) || []; + const assignedLogins = assignable.assignees?.nodes?.map((n: { login: string }) => n.login) || []; /** * Log assignees for debugging (but don't fail if Copilot is not in the list yet) @@ -618,91 +644,6 @@ export class GitHubService { } } - /** - * Get private key from environment variables or file - * - * @returns {string} Private key in PEM format with real newlines - * @throws {Error} If GITHUB_PRIVATE_KEY is not set - */ - private getPrivateKey(): string { - if (process.env.GITHUB_PRIVATE_KEY) { - /** - * Get private key from environment variable - * dotenv v16+ handles both multiline strings and escaped \n automatically - * But we check if there are literal \n sequences (not actual newlines) and replace them - */ - let privateKey = process.env.GITHUB_PRIVATE_KEY; - - /** - * Check if the string contains literal \n (backslash followed by n) instead of actual newlines - * This can happen if the value was stored as a single line with escaped newlines - */ - if (privateKey.includes('\\n') && !privateKey.includes('\n')) { - /** - * Replace literal \n with actual newlines - */ - privateKey = privateKey.replace(/\\n/g, '\n'); - } - - return privateKey; - } - - throw new Error('GITHUB_PRIVATE_KEY must be set'); - } - - /** - * Create JWT token for GitHub App authentication - * - * @returns {string} JWT token - */ - private createJWT(): string { - const privateKey = this.getPrivateKey(); - const now = Math.floor(Date.now() / 1000); - - /** - * JWT payload for GitHub App - * - iat: issued at time (current time) - * - exp: expiration time (10 minutes from now, GitHub allows up to 10 minutes) - * - iss: issuer (GitHub App ID) - */ - const payload = { - iat: now - 60, // Allow 1 minute clock skew - exp: now + 600, // 10 minutes expiration - iss: this.appId, - }; - - return jwt.sign(payload, privateKey, { algorithm: 'RS256' }); - } - - /** - * Get installation access token from GitHub API - * - * @param {string} installationId - GitHub App installation ID - * @returns {Promise} Installation access token (valid for 1 hour) - * @throws {Error} If token creation fails - */ - private async createInstallationToken(installationId: string): Promise { - const token = this.createJWT(); - - /** - * Create Octokit instance with JWT authentication and configured timeout - */ - const octokit = this.createOctokit(token); - - try { - /** - * Request installation access token - */ - const { data } = await octokit.rest.apps.createInstallationAccessToken({ - installation_id: parseInt(installationId, 10), - }); - - return data.token; - } catch (error) { - throw new Error(`Failed to create installation token: ${error instanceof Error ? error.message : String(error)}`); - } - } - /** * Exchange OAuth authorization code for user-to-server access token * This token allows the GitHub App to perform actions on behalf of the user @@ -746,7 +687,6 @@ export class GitHubService { redirectUrl: redirectUri, }); - if (!authentication.token) { throw new Error('No access token in OAuth response'); } @@ -791,8 +731,8 @@ export class GitHubService { * Validate user-to-server access token by making GET /user request * Updates tokenLastValidatedAt if validation succeeds * - * @param {string} accessToken - User-to-server access token - * @returns {Promise<{ valid: boolean; user?: { id: number; login: string }; status: 'active' | 'revoked' }>} Validation result + * @param accessToken - User-to-server access token + * @returns Validation result */ public async validateUserToken(accessToken: string): Promise<{ valid: boolean; user?: { id: number; login: string }; status: 'active' | 'revoked' }> { try { @@ -830,9 +770,9 @@ export class GitHubService { * Refresh user-to-server access token using refresh token * Rotates refresh token if a new one is provided * - * @param {string} refreshToken - OAuth refresh token - * @returns {Promise<{ accessToken: string; refreshToken: string; expiresAt: Date | null; refreshTokenExpiresAt: Date | null }>} New tokens - * @throws {Error} If token refresh fails + * @param refreshToken - OAuth refresh token + * @returns New tokens + * @throws If token refresh fails */ public async refreshUserToken(refreshToken: string): Promise<{ accessToken: string; @@ -874,4 +814,105 @@ export class GitHubService { throw new Error(`Failed to refresh user token: ${error instanceof Error ? error.message : String(error)}`); } } + + /** + * Create Octokit instance with configured timeout + * + * @param auth - Authentication token (JWT or installation access token) + * @returns Configured Octokit instance + */ + private createOctokit(auth: string): Octokit { + return new Octokit({ + auth, + request: { + timeout: GitHubService.DEFAULT_TIMEOUT, + }, + }); + } + + /** + * Get private key from environment variables or file + * + * @returns {string} Private key in PEM format with real newlines + * @throws {Error} If GITHUB_PRIVATE_KEY is not set + */ + private getPrivateKey(): string { + if (process.env.GITHUB_PRIVATE_KEY) { + /** + * Get private key from environment variable + * dotenv v16+ handles both multiline strings and escaped \n automatically + * But we check if there are literal \n sequences (not actual newlines) and replace them + */ + let privateKey = process.env.GITHUB_PRIVATE_KEY; + + /** + * Check if the string contains literal \n (backslash followed by n) instead of actual newlines + * This can happen if the value was stored as a single line with escaped newlines + */ + if (privateKey.includes('\\n') && !privateKey.includes('\n')) { + /** + * Replace literal \n with actual newlines + */ + privateKey = privateKey.replace(/\\n/g, '\n'); + } + + return privateKey; + } + + throw new Error('GITHUB_PRIVATE_KEY must be set'); + } + + /** + * Create JWT token for GitHub App authentication + * + * @returns {string} JWT token + */ + private createJWT(): string { + const privateKey = this.getPrivateKey(); + const now = Math.floor(Date.now() / 1000); + + /** + * JWT payload for GitHub App + * - iat: issued at time (current time) + * - exp: expiration time (10 minutes from now, GitHub allows up to 10 minutes) + * - iss: issuer (GitHub App ID) + */ + const payload = { + iat: now - 60, // Allow 1 minute clock skew + exp: now + 600, // 10 minutes expiration + iss: this.appId, + }; + + return jwt.sign(payload, privateKey, { algorithm: 'RS256' }); + } + + /** + * Get installation access token from GitHub API + * + * @param {string} installationId - GitHub App installation ID + * @returns {Promise} Installation access token (valid for 1 hour) + * @throws {Error} If token creation fails + */ + private async createInstallationToken(installationId: string): Promise { + const token = this.createJWT(); + + /** + * Create Octokit instance with JWT authentication and configured timeout + */ + const octokit = this.createOctokit(token); + + try { + /** + * Request installation access token + */ + const { data } = await octokit.rest.apps.createInstallationAccessToken({ + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: parseInt(installationId, 10), + }); + + return data.token; + } catch (error) { + throw new Error(`Failed to create installation token: ${error instanceof Error ? error.message : String(error)}`); + } + } } diff --git a/src/sso/saml/controller.ts b/src/sso/saml/controller.ts index 399d1c28..11d01d5d 100644 --- a/src/sso/saml/controller.ts +++ b/src/sso/saml/controller.ts @@ -154,6 +154,7 @@ export default class SamlController { res.redirect(redirectUrl.toString()); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + this.log( 'error', 'SSO initiation error for workspace:', @@ -271,6 +272,7 @@ export default class SamlController { if (!isValidRequest) { const requestIdShort = samlData.inResponseTo.slice(0, 8); + this.log( 'error', '[ACS] InResponseTo validation failed for workspace:', @@ -285,6 +287,7 @@ export default class SamlController { } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + this.log( 'error', '[ACS] SAML validation error for workspace:', @@ -387,6 +390,7 @@ export default class SamlController { */ if (error instanceof Error && error.message.includes('SAML')) { const errorMessage = error.message; + this.log( 'error', '[ACS] SAML processing error for workspace:', @@ -400,6 +404,7 @@ export default class SamlController { } const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + this.log( 'error', '[ACS] ACS callback error for workspace:', From fb0204d1f5563152e32c814b274f2149b7e034b5 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 16:30:15 +0300 Subject: [PATCH 12/15] fix tests --- package.json | 4 +- test/integrations/github.test.ts | 111 +++++--- test/models/businessOperation.test.ts | 4 +- test/models/businessOperationsFactory.test.ts | 4 +- test/models/user.test.ts | 4 +- test/models/usersFactory.test.ts | 4 +- yarn.lock | 252 +++++++++++------- 7 files changed, 240 insertions(+), 143 deletions(-) diff --git a/package.json b/package.json index 7322424d..7b540c27 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "devDependencies": { "@shelf/jest-mongodb": "^6.0.2", "@swc/core": "^1.3.0", - "@types/jest": "^27.5.0", + "@types/jest": "^29.5.0", "@types/xml2js": "^0.4.14", "eslint": "^6.7.2", "eslint-config-codex": "1.2.4", @@ -31,7 +31,7 @@ "mongodb-memory-server": "^6.6.1", "nodemon": "^2.0.2", "redis-mock": "^0.56.3", - "ts-jest": "^27.1.0", + "ts-jest": "^29.4.0", "ts-node": "^10.9.1", "typescript": "^4.7.4", "xml2js": "^0.6.2" diff --git a/test/integrations/github.test.ts b/test/integrations/github.test.ts index e7829ec2..187631f0 100644 --- a/test/integrations/github.test.ts +++ b/test/integrations/github.test.ts @@ -1,4 +1,6 @@ import '../../src/env-test'; +import { GitHubService } from '../../src/integrations/github/service'; +import jwt from 'jsonwebtoken'; /** * Mock @octokit/rest as virtual mock since module might not be installed in test environment @@ -13,14 +15,13 @@ jest.mock('@octokit/rest', () => ({ */ jest.mock('jsonwebtoken'); -import { GitHubService } from '../../src/integrations/github/service'; -import jwt from 'jsonwebtoken'; - describe('GitHubService', () => { let githubService: GitHubService; const testAppId = '123456'; const testAppSlug = 'hawk-tracker'; const testPrivateKey = '-----BEGIN RSA PRIVATE KEY-----\nTEST_KEY\n-----END RSA PRIVATE KEY-----'; + const testClientId = 'Iv1.client-id'; + const testClientSecret = 'client-secret'; const testInstallationId = '789012'; const testApiUrl = 'https://api.example.com'; @@ -35,13 +36,15 @@ describe('GitHubService', () => { addAssignees: jest.Mock; }; }; + graphql: jest.Mock; }; - const createMockOctokit = () => { + const createMockOctokit = (): typeof mockOctokit => { const createTokenMock = jest.fn(); const getInstallationMock = jest.fn(); const createIssueMock = jest.fn(); const addAssigneesMock = jest.fn(); + const graphqlMock = jest.fn(); return { rest: { @@ -54,6 +57,7 @@ describe('GitHubService', () => { addAssignees: addAssigneesMock, }, }, + graphql: graphqlMock, }; }; @@ -69,6 +73,8 @@ describe('GitHubService', () => { process.env.GITHUB_APP_ID = testAppId; process.env.GITHUB_APP_SLUG = testAppSlug; process.env.GITHUB_PRIVATE_KEY = testPrivateKey; + process.env.GITHUB_APP_CLIENT_ID = testClientId; + process.env.GITHUB_APP_CLIENT_SECRET = testClientSecret; process.env.API_URL = testApiUrl; /** @@ -79,8 +85,10 @@ describe('GitHubService', () => { /** * Get mocked Octokit constructor and set implementation */ + // eslint-disable-next-line @typescript-eslint/no-var-requires const { Octokit } = require('@octokit/rest'); - (Octokit as jest.Mock).mockImplementation(() => mockOctokit); + + Octokit.mockImplementation(() => mockOctokit); /** * Create service instance @@ -95,6 +103,8 @@ describe('GitHubService', () => { Reflect.deleteProperty(process.env, 'GITHUB_APP_ID'); Reflect.deleteProperty(process.env, 'GITHUB_APP_SLUG'); Reflect.deleteProperty(process.env, 'GITHUB_PRIVATE_KEY'); + Reflect.deleteProperty(process.env, 'GITHUB_APP_CLIENT_ID'); + Reflect.deleteProperty(process.env, 'GITHUB_APP_CLIENT_SECRET'); Reflect.deleteProperty(process.env, 'API_URL'); }); @@ -105,7 +115,7 @@ describe('GitHubService', () => { const url = githubService.getInstallationUrl(state); expect(url).toBe( - `https://github.com/apps/${testAppSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(`${testApiUrl}/integration/github/callback`)}` + `https://github.com/apps/${testAppSlug}/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent(`${testApiUrl}/integration/github/oauth`)}` ); }); @@ -126,6 +136,7 @@ describe('GitHubService', () => { it('should get installation information for User account', async () => { (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + /* eslint-disable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ mockOctokit.rest.apps.getInstallation.mockResolvedValue({ data: { id: 12345, @@ -143,6 +154,7 @@ describe('GitHubService', () => { }, }, } as any); + /* eslint-enable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ const result = await githubService.getInstallationForRepository(testInstallationId); @@ -152,6 +164,7 @@ describe('GitHubService', () => { login: 'octocat', type: 'User', }, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase target_type: 'User', permissions: { issues: 'write', @@ -160,6 +173,7 @@ describe('GitHubService', () => { }); expect(mockOctokit.rest.apps.getInstallation).toHaveBeenCalledWith({ + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installation_id: parseInt(testInstallationId, 10), }); }); @@ -167,6 +181,7 @@ describe('GitHubService', () => { it('should get installation information for Organization account', async () => { (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + /* eslint-disable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ mockOctokit.rest.apps.getInstallation.mockResolvedValue({ data: { id: 12345, @@ -184,6 +199,7 @@ describe('GitHubService', () => { }, }, } as any); + /* eslint-enable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ const result = await githubService.getInstallationForRepository(testInstallationId); @@ -193,6 +209,7 @@ describe('GitHubService', () => { login: 'my-org', type: 'Organization', }, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase target_type: 'Organization', permissions: { issues: 'write', @@ -219,21 +236,24 @@ describe('GitHubService', () => { beforeEach(() => { (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); + /* eslint-disable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ mockOctokit.rest.apps.createInstallationAccessToken.mockResolvedValue({ data: { token: mockInstallationToken, expires_at: '2025-01-01T00:00:00Z', }, } as any); + /* eslint-enable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ }); it('should create issue successfully', async () => { const issueData = { title: 'Test Issue', body: 'Test body', - labels: ['bug'], + labels: [ 'bug' ], }; + /* eslint-disable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ mockOctokit.rest.issues.create.mockResolvedValue({ data: { number: 123, @@ -242,11 +262,13 @@ describe('GitHubService', () => { state: 'open', }, } as any); + /* eslint-enable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ const result = await githubService.createIssue('owner/repo', testInstallationId, issueData); expect(result).toEqual({ number: 123, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase html_url: 'https://github.com/owner/repo/issues/123', title: 'Test Issue', state: 'open', @@ -257,7 +279,7 @@ describe('GitHubService', () => { repo: 'repo', title: 'Test Issue', body: 'Test body', - labels: ['bug'], + labels: [ 'bug' ], }); }); @@ -267,6 +289,7 @@ describe('GitHubService', () => { body: 'Test body', }; + /* eslint-disable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ mockOctokit.rest.issues.create.mockResolvedValue({ data: { number: 124, @@ -275,6 +298,7 @@ describe('GitHubService', () => { state: 'open', }, } as any); + /* eslint-enable @typescript-eslint/camelcase, camelcase, @typescript-eslint/no-explicit-any */ const result = await githubService.createIssue('owner/repo', testInstallationId, issueData); @@ -314,45 +338,64 @@ describe('GitHubService', () => { }); describe('assignCopilot', () => { - const mockJwtToken = 'mock-jwt-token'; - const mockInstallationToken = 'mock-installation-token'; - - beforeEach(() => { - (jwt.sign as jest.Mock).mockReturnValue(mockJwtToken); - - mockOctokit.rest.apps.createInstallationAccessToken.mockResolvedValue({ - data: { - token: mockInstallationToken, - expires_at: '2025-01-01T00:00:00Z', - }, - } as any); - }); + const mockDelegatedUserToken = 'mock-delegated-user-token'; it('should assign Copilot to issue successfully', async () => { const issueNumber = 123; - mockOctokit.rest.issues.addAssignees.mockResolvedValue({ - data: {}, - } as any); + mockOctokit.graphql + .mockResolvedValueOnce({ + repository: { + id: 'repo-123', + issue: { id: 'issue-456' }, + suggestedActors: { + nodes: [ + { + login: 'copilot-swe-agent', + __typename: 'Bot', + id: 'bot-789', + }, + ], + }, + }, + }) + .mockResolvedValueOnce({ + addAssigneesToAssignable: { + assignable: { + id: 'issue-456', + number: issueNumber, + assignees: { nodes: [] }, + }, + }, + }); - const result = await githubService.assignCopilot('owner', 'repo', issueNumber, testInstallationId); + await githubService.assignCopilot('owner/repo', issueNumber, mockDelegatedUserToken); - expect(result).toBe(true); - expect(mockOctokit.rest.issues.addAssignees).toHaveBeenCalledWith({ - owner: 'owner', - repo: 'repo', - issue_number: issueNumber, - assignees: ['github-copilot[bot]'], - }); + expect(mockOctokit.graphql).toHaveBeenCalledTimes(2); }); it('should throw error if assignment fails', async () => { const issueNumber = 123; - mockOctokit.rest.issues.addAssignees.mockRejectedValue(new Error('Issue not found')); + mockOctokit.graphql + .mockResolvedValueOnce({ + repository: { + id: 'repo-123', + issue: { id: 'issue-456' }, + suggestedActors: { + nodes: [ + { + login: 'copilot-swe-agent', + id: 'bot-789', + }, + ], + }, + }, + }) + .mockRejectedValue(new Error('Issue not found')); await expect( - githubService.assignCopilot('owner', 'repo', issueNumber, testInstallationId) + githubService.assignCopilot('owner/repo', issueNumber, mockDelegatedUserToken) ).rejects.toThrow('Failed to assign Copilot'); }); }); diff --git a/test/models/businessOperation.test.ts b/test/models/businessOperation.test.ts index 07d63915..e65cf80e 100644 --- a/test/models/businessOperation.test.ts +++ b/test/models/businessOperation.test.ts @@ -60,9 +60,7 @@ describe('Business operation model', () => { }); }); -afterAll(async done => { +afterAll(async () => { await mongo.mongoClients.hawk?.close(); await mongo.mongoClients.events?.close(); - - done(); }); diff --git a/test/models/businessOperationsFactory.test.ts b/test/models/businessOperationsFactory.test.ts index 29dc5a6f..35fc0f01 100644 --- a/test/models/businessOperationsFactory.test.ts +++ b/test/models/businessOperationsFactory.test.ts @@ -43,10 +43,8 @@ describe('Business operation factory', () => { }); -afterAll(async done => { +afterAll(async () => { await mongo.mongoClients.hawk?.close(); await mongo.mongoClients.events?.close(); - - done(); }); diff --git a/test/models/user.test.ts b/test/models/user.test.ts index aebee095..b0993da3 100644 --- a/test/models/user.test.ts +++ b/test/models/user.test.ts @@ -161,10 +161,8 @@ describe('UserModel SSO identities', () => { }); -afterAll(async done => { +afterAll(async () => { await mongo.mongoClients.hawk?.close(); await mongo.mongoClients.events?.close(); - - done(); }); diff --git a/test/models/usersFactory.test.ts b/test/models/usersFactory.test.ts index bc6fbfc6..3e1e8735 100644 --- a/test/models/usersFactory.test.ts +++ b/test/models/usersFactory.test.ts @@ -130,10 +130,8 @@ describe('UsersFactory SSO identities', () => { }); }); -afterAll(async done => { +afterAll(async () => { await mongo.mongoClients.hawk?.close(); await mongo.mongoClients.events?.close(); - - done(); }); diff --git a/yarn.lock b/yarn.lock index 612281e7..2c63c92b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -157,7 +157,7 @@ dependencies: "@babel/highlight" "^7.18.6" -"@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6": +"@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1", "@babel/code-frame@^7.28.6": version "7.28.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.28.6.tgz#72499312ec58b1e2245ba4a4f550c132be4982f7" integrity sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q== @@ -652,6 +652,13 @@ dependencies: "@jest/get-type" "30.1.0" +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== + dependencies: + jest-get-type "^29.6.3" + "@jest/expect@30.2.0": version "30.2.0" resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-30.2.0.tgz#9a5968499bb8add2bbb09136f69f7df5ddbf3185" @@ -731,6 +738,13 @@ dependencies: "@sinclair/typebox" "^0.34.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/snapshot-utils@30.2.0": version "30.2.0" resolved "https://registry.yarnpkg.com/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz#387858eb90c2f98f67bff327435a532ac5309fbe" @@ -804,15 +818,16 @@ "@types/yargs" "^17.0.33" chalk "^4.1.2" -"@jest/types@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" - integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" - "@types/yargs" "^16.0.0" + "@types/yargs" "^17.0.8" chalk "^4.0.0" "@josephg/resolvable@^1.0.0": @@ -1176,6 +1191,11 @@ debug "4.4.1" mongodb-memory-server "10.3.0" +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinclair/typebox@^0.34.0": version "0.34.41" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" @@ -1529,13 +1549,13 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^27.5.0": - version "27.5.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" - integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== +"@types/jest@^29.5.0": + version "29.5.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.14.tgz#2b910912fa1d6856cadcd0c1f95af7df1d6049e5" + integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== dependencies: - jest-matcher-utils "^27.0.0" - pretty-format "^27.0.0" + expect "^29.0.0" + pretty-format "^29.0.0" "@types/json-schema@^7.0.3": version "7.0.11" @@ -1705,7 +1725,7 @@ "@types/mime" "*" "@types/node" "*" -"@types/stack-utils@^2.0.3": +"@types/stack-utils@^2.0.0", "@types/stack-utils@^2.0.3": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== @@ -1761,14 +1781,7 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@^16.0.0": - version "16.0.11" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.11.tgz#de958fb62e77fc383fa6cd8066eabdd13da88f04" - integrity sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.33": +"@types/yargs@^17.0.33", "@types/yargs@^17.0.8": version "17.0.35" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== @@ -2493,7 +2506,7 @@ browserslist@^4.24.0: node-releases "^2.0.27" update-browserslist-db "^1.2.0" -bs-logger@0.x: +bs-logger@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== @@ -2990,10 +3003,10 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" -diff-sequences@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" - integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: version "4.0.2" @@ -3459,6 +3472,17 @@ expect@30.2.0: jest-mock "30.2.0" jest-util "30.2.0" +expect@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== + dependencies: + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + express@^4.17.1: version "4.18.1" resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" @@ -3905,6 +3929,18 @@ graphql-upload@^13: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== +handlebars@^4.7.8: + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.2" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -4424,15 +4460,15 @@ jest-diff@30.2.0: chalk "^4.1.2" pretty-format "30.2.0" -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-docblock@30.2.0: version "30.2.0" @@ -4465,10 +4501,10 @@ jest-environment-node@30.2.0: jest-util "30.2.0" jest-validate "30.2.0" -jest-get-type@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" - integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== jest-haste-map@30.2.0: version "30.2.0" @@ -4506,15 +4542,15 @@ jest-matcher-utils@30.2.0: jest-diff "30.2.0" pretty-format "30.2.0" -jest-matcher-utils@^27.0.0: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== dependencies: chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" jest-message-util@30.2.0: version "30.2.0" @@ -4531,6 +4567,21 @@ jest-message-util@30.2.0: slash "^3.0.0" stack-utils "^2.0.6" +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^29.6.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^29.7.0" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@30.2.0: version "30.2.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-30.2.0.tgz#69f991614eeb4060189459d3584f710845bff45e" @@ -4667,12 +4718,12 @@ jest-util@30.2.0: graceful-fs "^4.2.11" picomatch "^4.0.2" -jest-util@^27.0.0: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" - integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" @@ -4769,11 +4820,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json5@2.x: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -4911,7 +4957,7 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== -lodash.memoize@4.x: +lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -4982,7 +5028,7 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -5029,7 +5075,7 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.8: +micromatch@^4.0.4, micromatch@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -5092,6 +5138,11 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +minimist@^1.2.5: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" @@ -5255,6 +5306,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + new-find-package-json@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/new-find-package-json/-/new-find-package-json-2.0.0.tgz#96553638781db35061f351e8ccb4d07126b6407d" @@ -5637,14 +5693,14 @@ pretty-format@30.2.0: ansi-styles "^5.2.0" react-is "^18.3.1" -pretty-format@^27.0.0, pretty-format@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" - integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== +pretty-format@^29.0.0, pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - ansi-regex "^5.0.1" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" - react-is "^17.0.1" + react-is "^18.0.0" process-nextick-args@~2.0.0: version "2.0.1" @@ -5741,12 +5797,7 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -react-is@^18.3.1: +react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== @@ -5985,13 +6036,6 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.3.2, semver@^7.3.5: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - semver@^6.0.0, semver@^6.1.0, semver@^6.1.2: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -6002,6 +6046,13 @@ semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.2, semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + semver@^7.5.4, semver@^7.7.2, semver@^7.7.3: version "7.7.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" @@ -6174,7 +6225,7 @@ source-map-support@^0.5.12: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: +source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -6222,7 +6273,7 @@ stack-trace@^0.0.10: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== -stack-utils@^2.0.6: +stack-utils@^2.0.3, stack-utils@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== @@ -6541,19 +6592,20 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -ts-jest@^27.1.0: - version "27.1.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" - integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== +ts-jest@^29.4.0: + version "29.4.6" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.4.6.tgz#51cb7c133f227396818b71297ad7409bb77106e9" + integrity sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA== dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" - jest-util "^27.0.0" - json5 "2.x" - lodash.memoize "4.x" - make-error "1.x" - semver "7.x" - yargs-parser "20.x" + bs-logger "^0.2.6" + fast-json-stable-stringify "^2.1.0" + handlebars "^4.7.8" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.7.3" + type-fest "^4.41.0" + yargs-parser "^21.1.1" ts-node-dev@^2.0.0: version "2.0.0" @@ -6659,6 +6711,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^4.41.0: + version "4.41.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58" + integrity sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -6677,6 +6734,11 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +uglify-js@^3.1.4: + version "3.19.3" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -6924,6 +6986,11 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -7063,11 +7130,6 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yargs-parser@20.x: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" From 8c4c3c3693aa2004070fe6fa3551fc17e5eed31a Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 21:25:38 +0300 Subject: [PATCH 13/15] fix integration tests due to jest 30 update --- docker-compose.test.yml | 15 ++++++----- test/integration/api.env | 14 ++++++++++ test/integration/jestEnv.js | 4 +-- test/integration/keycloak/setup.sh | 41 ++++++++++++++++++++++-------- test/integration/types/global.d.ts | 31 +++++++++++++--------- 5 files changed, 73 insertions(+), 32 deletions(-) diff --git a/docker-compose.test.yml b/docker-compose.test.yml index be1d3a69..f2130be2 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,4 +1,3 @@ -version: "3.4" services: api: build: @@ -16,7 +15,6 @@ services: - mongodb - rabbitmq - keycloak - # - accounting stdin_open: true tty: true @@ -65,7 +63,7 @@ services: retries: 5 keycloak: - image: quay.io/keycloak/keycloak:23.0 + image: quay.io/keycloak/keycloak:22.0 environment: - KEYCLOAK_ADMIN=admin - KEYCLOAK_ADMIN_PASSWORD=admin @@ -74,18 +72,21 @@ services: - KC_HOSTNAME_STRICT_HTTPS=false - KC_HTTP_ENABLED=true - KC_HEALTH_ENABLED=true + - JAVA_OPTS_APPEND=-Djava.io.tmpdir=/opt/keycloak/data/tmp ports: - 8180:8180 command: - start-dev volumes: - keycloak-test-data:/opt/keycloak/data - - ./test/integration/keycloak:/opt/keycloak/config + tmpfs: + - /tmp:size=128M healthcheck: - test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/8180;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;"] + test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8180 && echo -e 'GET /health/ready HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3 && cat <&3 | grep -q '200 OK'"] interval: 10s - timeout: 5s - retries: 10 + timeout: 10s + retries: 15 + start_period: 60s # accounting: # image: codexteamuser/codex-accounting:prod diff --git a/test/integration/api.env b/test/integration/api.env index fe3384e4..80f085d0 100644 --- a/test/integration/api.env +++ b/test/integration/api.env @@ -88,3 +88,17 @@ UPLOAD_DIR=uploads ## Hawk Catcher token from hawk.so HAWK_CATCHER_TOKEN= + +# Id of GitHub app +GITHUB_APP_ID=2000000 + +# GitHub App slug/name. Used to generate installation URLs +GITHUB_APP_SLUG=hawk-test-app + +# Private key generated in GitHub app settings +GITHUB_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY----- +MYKEY== +-----END RSA PRIVATE KEY-----" + +GITHUB_APP_CLIENT_ID=Ivvv3333vvv3333vv +GITHUB_APP_CLIENT_SECRET=02da32efc3223defc32ede30192c03020d023d03e diff --git a/test/integration/jestEnv.js b/test/integration/jestEnv.js index cad69c67..185ff71a 100644 --- a/test/integration/jestEnv.js +++ b/test/integration/jestEnv.js @@ -1,4 +1,4 @@ -const NodeEnvironment = require('jest-environment-node'); +const { TestEnvironment } = require('jest-environment-node'); const amqp = require('amqplib'); const mongodb = require('mongodb'); const { installRedisMock, uninstallRedisMock } = require('./redisMock'); @@ -6,7 +6,7 @@ const { installRedisMock, uninstallRedisMock } = require('./redisMock'); /** * Custom test environment for defining global connections */ -class CustomEnvironment extends NodeEnvironment { +class CustomEnvironment extends TestEnvironment { /** * Setup environment * @return {Promise} diff --git a/test/integration/keycloak/setup.sh b/test/integration/keycloak/setup.sh index 28c95092..d74aa3b6 100755 --- a/test/integration/keycloak/setup.sh +++ b/test/integration/keycloak/setup.sh @@ -43,25 +43,44 @@ if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then exit 1 fi -# Get admin token +# Additional wait for admin user to be fully initialized +echo "⏳ Waiting for admin user to be ready..." +sleep 3 + +# Get admin token with retries echo "🔑 Obtaining admin token..." -TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "username=$ADMIN_USER" \ - -d "password=$ADMIN_PASSWORD" \ - -d "grant_type=password" \ - -d "client_id=admin-cli") +TOKEN_RETRIES=10 +TOKEN_RETRY_COUNT=0 +ACCESS_TOKEN="" + +while [ $TOKEN_RETRY_COUNT -lt $TOKEN_RETRIES ]; do + TOKEN_RESPONSE=$(curl -s -X POST "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "username=$ADMIN_USER" \ + -d "password=$ADMIN_PASSWORD" \ + -d "grant_type=password" \ + -d "client_id=admin-cli") + + ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) + + if [ -n "$ACCESS_TOKEN" ]; then + echo "✓ Admin token obtained" + break + fi -ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) + TOKEN_RETRY_COUNT=$((TOKEN_RETRY_COUNT + 1)) + if [ $TOKEN_RETRY_COUNT -lt $TOKEN_RETRIES ]; then + echo "Retrying token request... ($TOKEN_RETRY_COUNT/$TOKEN_RETRIES)" + sleep 2 + fi +done if [ -z "$ACCESS_TOKEN" ]; then - echo "❌ Failed to obtain admin token" + echo "❌ Failed to obtain admin token after $TOKEN_RETRIES attempts" echo "Response: $TOKEN_RESPONSE" exit 1 fi -echo "✓ Admin token obtained" - # Check if realm already exists echo "🔍 Checking if realm '$REALM_NAME' exists..." REALM_EXISTS=$(curl -s -o /dev/null -w "%{http_code}" "$KEYCLOAK_URL/admin/realms/$REALM_NAME" \ diff --git a/test/integration/types/global.d.ts b/test/integration/types/global.d.ts index 49a3eaa8..4ba97a2b 100644 --- a/test/integration/types/global.d.ts +++ b/test/integration/types/global.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-var */ /* eslint-disable @typescript-eslint/no-unused-vars */ import type { MongoClient } from 'mongodb'; import { Channel } from 'amqplib'; @@ -6,17 +7,23 @@ import { Channel } from 'amqplib'; * Defines global variables for using in tests */ declare global { - namespace NodeJS { - interface Global { - /** - * MongoDB client instance - */ - mongoClient: MongoClient; + /** + * MongoDB client instance + */ + var mongoClient: MongoClient; - /** - * RabbitMQ client instance - */ - rabbitChannel: Channel; - } - } + /** + * RabbitMQ channel instance + */ + var rabbitChannel: Channel; + + /** + * Redis client instance (mock) + */ + var redisClient: unknown; + + /** + * Performance API for MongoDB driver + */ + var performance: Performance; } From 805daab5102b790eba85bc32fbbaff457ee0e158 Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 22:00:57 +0300 Subject: [PATCH 14/15] Improve GitHub integration error handling and tests Refactored GitHub OAuth and installation flow to provide more accurate error redirects and preserve existing taskManager config values. Enhanced timing-safe signature validation using crypto.timingSafeEqual. Updated GraphQL query in GitHubService to use variable for issue number. Expanded and improved test coverage for GitHub routes, including edge cases and config preservation. Refactored project resolver tests for better type safety and error handling. --- src/integrations/github/routes.ts | 36 +- src/integrations/github/service.ts | 5 +- test/integrations/github-routes.test.ts | 666 +++++++++++++++++++++++- test/resolvers/project.test.ts | 54 +- 4 files changed, 700 insertions(+), 61 deletions(-) diff --git a/src/integrations/github/routes.ts b/src/integrations/github/routes.ts index 4c17dade..840af121 100644 --- a/src/integrations/github/routes.ts +++ b/src/integrations/github/routes.ts @@ -2,7 +2,7 @@ import '../../typeDefs/expressContext'; import express from 'express'; import { v4 as uuid } from 'uuid'; import { ObjectId } from 'mongodb'; -import { createHmac } from 'crypto'; +import { createHmac, timingSafeEqual } from 'crypto'; import { GitHubService } from './service'; import { ProjectDBScheme } from '@hawk.so/types'; import { ContextFactories } from '../../types/graphql'; @@ -289,13 +289,13 @@ export function createGitHubRouter(factories: ContextFactories): express.Router * Validate required parameters */ if (!code || typeof code !== 'string') { - return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + return res.redirect(buildGarageRedirectUrl('/', { error: 'Missing or invalid OAuth code', })); } if (!state || typeof state !== 'string') { - return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + return res.redirect(buildGarageRedirectUrl('/', { error: 'Missing or invalid state', })); } @@ -309,7 +309,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router if (!stateData) { log('warn', `Invalid or expired state: ${sgr(state.slice(0, 8), Effect.ForegroundGray)}...`); - return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + return res.redirect(buildGarageRedirectUrl('/', { error: 'Invalid or expired state. Please try connecting again.', })); } @@ -326,7 +326,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router if (!project) { log('error', projectId, 'Project not found'); - return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { error: `Project not found: ${projectId}`, })); } @@ -381,6 +381,9 @@ export function createGitHubRouter(factories: ContextFactories): express.Router ...project.taskManager.config, // eslint-disable-next-line @typescript-eslint/camelcase, camelcase installationId: installation_id, + // Preserve existing repoId and repoFullName if they exist, otherwise use defaults + repoId: project.taskManager.config.repoId || taskManagerConfig.config.repoId, + repoFullName: project.taskManager.config.repoFullName || taskManagerConfig.config.repoFullName, }, } : taskManagerConfig, } as Partial); @@ -402,7 +405,7 @@ export function createGitHubRouter(factories: ContextFactories): express.Router if (!updatedProject) { log('error', projectId, 'Project not found after update'); - return res.redirect(buildGarageRedirectUrl('/project/error/settings/task-manager', { + return res.redirect(buildGarageRedirectUrl(`/project/${projectId}/settings/task-manager`, { error: `Project not found: ${projectId}`, })); } @@ -543,19 +546,26 @@ export function createGitHubRouter(factories: ContextFactories): express.Router /** * Use timing-safe comparison to prevent timing attacks + * timingSafeEqual requires both arguments to be Buffer or Uint8Array of the same length */ let signatureValid = false; if (signature.length === calculatedSignature.length) { - let match = true; + try { + const signatureBuffer = Buffer.from(signature); + const calculatedBuffer = Buffer.from(calculatedSignature); - for (let i = 0; i < signature.length; i++) { - if (signature[i] !== calculatedSignature[i]) { - match = false; - } + signatureValid = timingSafeEqual( + signatureBuffer as Uint8Array, + calculatedBuffer as Uint8Array + ); + } catch (error) { + /** + * timingSafeEqual throws if buffers have different lengths + * This shouldn't happen due to the length check above, but handle it gracefully + */ + signatureValid = false; } - - signatureValid = match; } if (!signatureValid) { diff --git a/src/integrations/github/service.ts b/src/integrations/github/service.ts index 301dd8b4..8ba82b95 100644 --- a/src/integrations/github/service.ts +++ b/src/integrations/github/service.ts @@ -435,10 +435,10 @@ export class GitHubService { * Step 1: Get repository ID and find Copilot bot ID */ const repoInfoQuery = ` - query($owner: String!, $name: String!) { + query($owner: String!, $name: String!, $issueNumber: Int!) { repository(owner: $owner, name: $name) { id - issue(number: ${issueNumber}) { + issue(number: $issueNumber) { id } suggestedActors(capabilities: [CAN_BE_ASSIGNED], first: 100) { @@ -470,6 +470,7 @@ export class GitHubService { const repoInfo = await octokit.graphql(repoInfoQuery, { owner, name: repo, + issueNumber, }); console.log('[GitHub API] Repository info query response:', JSON.stringify(repoInfo, null, 2)); diff --git a/test/integrations/github-routes.test.ts b/test/integrations/github-routes.test.ts index a72ce16f..6a73b2f0 100644 --- a/test/integrations/github-routes.test.ts +++ b/test/integrations/github-routes.test.ts @@ -1,28 +1,34 @@ import '../../src/env-test'; import { ObjectId } from 'mongodb'; import express from 'express'; -import type { Request, Response } from 'express'; import { createGitHubRouter } from '../../src/integrations/github/routes'; import { ContextFactories } from '../../src/types/graphql'; -import WorkspaceModel from '../../src/models/workspace'; /** * Mock GitHubService */ +const mockGetInstallationUrl = jest.fn((state: string) => { + return `https://github.com/apps/test-app/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent('http://localhost:4000/integration/github/oauth')}`; +}); +const mockGetInstallationForRepository = jest.fn(); +const mockExchangeOAuthCodeForToken = jest.fn(); + jest.mock('../../src/integrations/github/service', () => ({ GitHubService: jest.fn().mockImplementation(() => ({ - getInstallationUrl: jest.fn((state: string) => { - return `https://github.com/apps/test-app/installations/new?state=${encodeURIComponent(state)}&redirect_url=${encodeURIComponent('http://localhost:4000/integration/github/callback')}`; - }), + getInstallationUrl: mockGetInstallationUrl, + getInstallationForRepository: mockGetInstallationForRepository, + exchangeOAuthCodeForToken: mockExchangeOAuthCodeForToken, })), })); /** * Mock install state store */ +const mockGetState = jest.fn(); jest.mock('../../src/integrations/github/store/install-state.redis.store', () => ({ RedisInstallStateStore: jest.fn().mockImplementation(() => ({ saveState: jest.fn().mockResolvedValue(undefined), + getState: mockGetState, })), })); @@ -99,17 +105,29 @@ function makeRequest( }, json: (data: any) => { jsonCalled = true; - resolve({ status: statusCode, body: data }); + resolve({ + status: statusCode, + body: data, + }); }, setHeader: jest.fn(), getHeader: jest.fn(), end: jest.fn(), send: jest.fn((data?: any) => { if (!jsonCalled) { - resolve({ status: statusCode, body: data }); + resolve({ + status: statusCode, + body: data, + }); } }), - redirect: jest.fn(), + redirect: jest.fn((redirectUrl: string) => { + statusCode = 302; + resolve({ + status: statusCode, + body: redirectUrl, + }); + }), } as any; /** @@ -125,7 +143,10 @@ function makeRequest( * Wait a bit to allow async handlers to complete */ setTimeout(() => { - resolve({ status: statusCode, body: null }); + resolve({ + status: statusCode, + body: null, + }); }, 50); } }); @@ -176,12 +197,16 @@ describe('GitHub Routes - /integration/github/connect', () => { beforeEach(() => { jest.clearAllMocks(); + mockGetState.mockReset(); + mockGetInstallationForRepository.mockReset(); + mockExchangeOAuthCodeForToken.mockReset(); /** * Setup environment variables */ process.env.GITHUB_APP_ID = '123456'; process.env.API_URL = 'http://localhost:4000'; + process.env.GARAGE_URL = 'http://localhost:8080'; }); afterEach(() => { @@ -193,8 +218,14 @@ describe('GitHub Routes - /integration/github/connect', () => { it('should return JSON with redirectUrl when user is authenticated and is admin', async () => { const projectId = new ObjectId().toString(); const workspaceId = new ObjectId().toString(); - const mockProject = createMockProject({ projectId, workspaceId }); - const mockWorkspace = createMockWorkspace({ workspaceId, isAdmin: true }); + const mockProject = createMockProject({ + projectId, + workspaceId, + }); + const mockWorkspace = createMockWorkspace({ + workspaceId, + isAdmin: true, + }); const factories: ContextFactories = { projectsFactory: { @@ -330,8 +361,14 @@ describe('GitHub Routes - /integration/github/connect', () => { it('should return 403 when user is not admin', async () => { const projectId = new ObjectId().toString(); const workspaceId = new ObjectId().toString(); - const mockProject = createMockProject({ projectId, workspaceId }); - const mockWorkspace = createMockWorkspace({ workspaceId, isAdmin: false }); + const mockProject = createMockProject({ + projectId, + workspaceId, + }); + const mockWorkspace = createMockWorkspace({ + workspaceId, + isAdmin: false, + }); const factories: ContextFactories = { projectsFactory: { @@ -358,8 +395,14 @@ describe('GitHub Routes - /integration/github/connect', () => { it('should return 403 when user is not a member of workspace', async () => { const projectId = new ObjectId().toString(); const workspaceId = new ObjectId().toString(); - const mockProject = createMockProject({ projectId, workspaceId }); - const mockWorkspace = createMockWorkspace({ workspaceId, member: null }); + const mockProject = createMockProject({ + projectId, + workspaceId, + }); + const mockWorkspace = createMockWorkspace({ + workspaceId, + member: null, + }); const factories: ContextFactories = { projectsFactory: { @@ -383,4 +426,597 @@ describe('GitHub Routes - /integration/github/connect', () => { expect(response.body.error).toContain('not a member'); }); }); + + describe('GET /integration/github/oauth', () => { + const projectId = new ObjectId().toString(); + const workspaceId = new ObjectId().toString(); + const state = 'test-state-123'; + const code = 'test-oauth-code'; + const installationId = '12345678'; + + /** + * Helper to create mock project with taskManager + */ + function createMockProjectWithTaskManager(taskManager?: any): any { + const mockProject = createMockProject({ + projectId, + workspaceId, + }); + + return { + ...mockProject, + taskManager: taskManager || { + type: 'github', + autoTaskEnabled: false, + taskThresholdTotalCount: 50, + assignAgent: false, + connectedAt: new Date('2025-01-01'), + updatedAt: new Date('2025-01-01'), + config: { + installationId: installationId, + repoId: '789012', + repoFullName: 'owner/repo', + }, + }, + updateProject: jest.fn().mockResolvedValue(undefined), + }; + } + + it('should redirect with error when code is missing', async () => { + const factories: ContextFactories = { + projectsFactory: {} as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + state, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain('http://localhost:8080/'); + expect(response.body).toContain('error=Missing+or+invalid+OAuth+code'); + }); + + it('should redirect with error when state is missing', async () => { + const factories: ContextFactories = { + projectsFactory: {} as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain('http://localhost:8080/'); + expect(response.body).toContain('error=Missing+or+invalid+state'); + }); + + it('should redirect with error when state is invalid or expired', async () => { + mockGetState.mockResolvedValue(null); + + const factories: ContextFactories = { + projectsFactory: {} as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain('http://localhost:8080/'); + expect(response.body).toContain('error=Invalid+or+expired+state'); + expect(mockGetState).toHaveBeenCalledWith(state); + }); + + it('should redirect with error when project is not found', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(null), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=Project+not+found'); + }); + + it('should redirect with error when installation_id is present but getInstallationForRepository fails', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProjectWithTaskManager(); + mockGetInstallationForRepository.mockRejectedValue(new Error('Installation not found')); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: installationId, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=Failed+to+retrieve+GitHub+installation+information'); + expect(mockGetInstallationForRepository).toHaveBeenCalledWith(installationId); + }); + + it('should redirect with error when saving taskManager config fails', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProjectWithTaskManager(); + mockProject.updateProject.mockRejectedValue(new Error('Database error')); + mockGetInstallationForRepository.mockResolvedValue({}); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: installationId, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=Failed+to+save+Task+Manager+configuration'); + }); + + it('should redirect with error when project is not found after update', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProjectWithTaskManager(); + mockGetInstallationForRepository.mockResolvedValue({}); + mockProject.updateProject.mockResolvedValue(undefined); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn() + .mockResolvedValueOnce(mockProject) // First call - project exists + .mockResolvedValueOnce(null), // Second call after update - project not found + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: installationId, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=Project+not+found'); + }); + + it('should redirect with error when project does not have taskManager after installation', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProject({ + projectId, + workspaceId, + }); + mockProject.taskManager = null; + mockGetInstallationForRepository.mockResolvedValue({}); + mockProject.updateProject.mockResolvedValue(undefined); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: installationId, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=GitHub+App+installation+failed'); + }); + + it('should redirect with error when exchangeOAuthCodeForToken fails', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProjectWithTaskManager(); + mockExchangeOAuthCodeForToken.mockRejectedValue(new Error('Invalid code')); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=Failed+to+exchange+OAuth+code+for+token'); + expect(mockExchangeOAuthCodeForToken).toHaveBeenCalledWith(code); + }); + + it('should redirect with error when saving delegatedUser fails', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProjectWithTaskManager(); + mockProject.updateProject.mockRejectedValueOnce(new Error('Database error')); + mockExchangeOAuthCodeForToken.mockResolvedValue({ + user: { + id: 'github-user-123', + login: 'testuser', + }, + accessToken: 'token-123', + expiresAt: new Date(), + refreshToken: 'refresh-123', + refreshTokenExpiresAt: new Date(), + }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('error=Failed+to+save+OAuth+token'); + }); + + /** + * Scenario: GitHub App is already installed, user authorizes via OAuth to get access token + * This happens when: + * 1. GitHub App was installed earlier (taskManager config already exists with installationId) + * 2. User clicks "Connect" again or needs to re-authorize + * 3. GitHub redirects back with OAuth code (but no installation_id, since installation already exists) + * Expected: OAuth code is exchanged for token, delegatedUser is saved to existing taskManager config + */ + it('should save OAuth token when GitHub App is already installed (no installation_id in callback)', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + /** + * Project already has taskManager config with installationId from previous installation + */ + const mockProject = createMockProjectWithTaskManager(); + mockProject.updateProject.mockResolvedValue(undefined); + + /** + * Mock successful OAuth token exchange + */ + mockExchangeOAuthCodeForToken.mockResolvedValue({ + user: { + id: 'github-user-123', + login: 'testuser', + }, + accessToken: 'token-123', + expiresAt: new Date('2025-12-31'), + refreshToken: 'refresh-123', + refreshTokenExpiresAt: new Date('2026-12-31'), + }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + /** + * OAuth callback without installation_id (installation already exists) + */ + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + }); + + /** + * Should redirect to settings page with success + */ + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('success=true'); + + /** + * Should exchange OAuth code for token + */ + expect(mockExchangeOAuthCodeForToken).toHaveBeenCalledWith(code); + + /** + * Should save delegatedUser to existing taskManager config + */ + expect(mockProject.updateProject).toHaveBeenCalledWith( + expect.objectContaining({ + taskManager: expect.objectContaining({ + config: expect.objectContaining({ + delegatedUser: expect.objectContaining({ + hawkUserId: userId, + githubUserId: 'github-user-123', + githubLogin: 'testuser', + accessToken: 'token-123', + }), + }), + }), + }) + ); + }); + + it('should successfully complete OAuth flow with installation_id', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const mockProject = createMockProject({ + projectId, + workspaceId, + }); + mockProject.taskManager = null; + mockProject.updateProject = jest.fn().mockResolvedValue(undefined); + + mockGetInstallationForRepository.mockResolvedValue({}); + mockExchangeOAuthCodeForToken.mockResolvedValue({ + user: { + id: 'github-user-123', + login: 'testuser', + }, + accessToken: 'token-123', + expiresAt: new Date('2025-12-31'), + refreshToken: 'refresh-123', + refreshTokenExpiresAt: new Date('2026-12-31'), + }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn() + .mockResolvedValueOnce(mockProject) // First call - before installation + .mockResolvedValueOnce({ // Second call - after installation update + ...mockProject, + taskManager: { + type: 'github', + autoTaskEnabled: false, + taskThresholdTotalCount: 50, + assignAgent: false, + connectedAt: expect.any(Date), + updatedAt: expect.any(Date), + config: { + installationId: installationId, + repoId: '', + repoFullName: '', + }, + }, + }), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: installationId, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain(`/project/${projectId}/settings/task-manager`); + expect(response.body).toContain('success=true'); + expect(mockGetInstallationForRepository).toHaveBeenCalledWith(installationId); + expect(mockExchangeOAuthCodeForToken).toHaveBeenCalledWith(code); + expect(mockProject.updateProject).toHaveBeenCalledTimes(2); // Once for installation, once for delegatedUser + }); + + it('should preserve existing taskManager config when updating with installation_id', async () => { + mockGetState.mockResolvedValue({ + projectId, + userId, + timestamp: Date.now(), + }); + + const existingConfig = { + installationId: 'old-installation-id', + repoId: 'existing-repo-id', + repoFullName: 'existing/owner-repo', + delegatedUser: { + hawkUserId: userId, + githubUserId: 'old-github-user', + githubLogin: 'olduser', + accessToken: 'old-token', + }, + }; + + const mockProject = createMockProjectWithTaskManager({ + type: 'github', + autoTaskEnabled: true, + taskThresholdTotalCount: 100, + assignAgent: true, + connectedAt: new Date('2024-01-01'), + updatedAt: new Date('2024-01-01'), + config: existingConfig, + }); + mockProject.updateProject = jest.fn().mockResolvedValue(undefined); + + mockGetInstallationForRepository.mockResolvedValue({}); + mockExchangeOAuthCodeForToken.mockResolvedValue({ + user: { + id: 'github-user-123', + login: 'testuser', + }, + accessToken: 'token-123', + expiresAt: new Date('2025-12-31'), + refreshToken: 'refresh-123', + refreshTokenExpiresAt: new Date('2026-12-31'), + }); + + const factories: ContextFactories = { + projectsFactory: { + findById: jest.fn().mockResolvedValue(mockProject), + } as any, + workspacesFactory: {} as any, + usersFactory: {} as any, + plansFactory: {} as any, + businessOperationsFactory: {} as any, + releasesFactory: {} as any, + }; + + setupRouter(factories); + + const response = await makeRequest(app, 'GET', '/integration/github/oauth', { + code, + state, + // eslint-disable-next-line @typescript-eslint/camelcase, camelcase + installation_id: installationId, + }); + + expect(response.status).toBe(302); + expect(response.body).toContain('success=true'); + + /** + * Check that first update (installation) preserves existing config + */ + const firstUpdateCall = mockProject.updateProject.mock.calls[0]; + expect(firstUpdateCall[0].taskManager.config.installationId).toBe(installationId); + expect(firstUpdateCall[0].taskManager.config.repoId).toBe('existing-repo-id'); + expect(firstUpdateCall[0].taskManager.config.repoFullName).toBe('existing/owner-repo'); + expect(firstUpdateCall[0].taskManager.config.delegatedUser).toEqual(existingConfig.delegatedUser); + }); + }); }); diff --git a/test/resolvers/project.test.ts b/test/resolvers/project.test.ts index d9dcad87..10f940b5 100644 --- a/test/resolvers/project.test.ts +++ b/test/resolvers/project.test.ts @@ -1,31 +1,23 @@ import '../../src/env-test'; import { ObjectId } from 'mongodb'; -import { ProjectDBScheme } from '@hawk.so/types'; +import { ProjectDBScheme, ProjectTaskManagerConfig } from '@hawk.so/types'; +import { ResolverContextWithUser } from '../../src/types/graphql'; +import { ApolloError, UserInputError } from 'apollo-server-express'; +// @ts-expect-error - CommonJS module, TypeScript can't infer types properly +import projectResolverModule from '../../src/resolvers/project'; /** - * Task Manager configuration type (matching ProjectTaskManagerConfig from @hawk.so/types) + * Type assertion for CommonJS module */ -type ProjectTaskManagerConfig = { - type: 'github'; - autoTaskEnabled: boolean; - taskThresholdTotalCount: number; - assignAgent: boolean; - usage?: { - dayStartUtc: Date; - autoTasksCreated: number; +const projectResolver = projectResolverModule as { + Mutation: { + disconnectTaskManager: (...args: unknown[]) => Promise; + updateTaskManagerSettings: (...args: unknown[]) => Promise; }; - connectedAt: Date; - updatedAt: Date; - config: { - installationId: string; - repoId: string | number; - repoFullName: string; + Query: { + project: (...args: unknown[]) => Promise; }; }; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const projectResolver = require('../../src/resolvers/project'); -import { ResolverContextWithUser } from '../../src/types/graphql'; -import { ApolloError, UserInputError } from 'apollo-server-express'; // Set environment variables for test process.env.JWT_SECRET_ACCESS_TOKEN = 'belarus'; @@ -48,7 +40,7 @@ function createMockProject(options: { _id: ObjectId; workspaceId: ObjectId; updateProject: jest.Mock; - taskManager?: ProjectTaskManagerConfig | null; + taskManager?: ProjectTaskManagerConfig; } { const { projectId = new ObjectId().toString(), @@ -68,7 +60,7 @@ function createMockProject(options: { token: 'test-token', notifications: [], eventGroupingPatterns: [], - taskManager: data.taskManager !== undefined ? data.taskManager : taskManager, + taskManager: data.taskManager !== undefined ? (data.taskManager ?? undefined) : (taskManager ?? undefined), }; }); @@ -80,7 +72,7 @@ function createMockProject(options: { token: 'test-token', notifications: [], eventGroupingPatterns: [], - taskManager, + taskManager: taskManager ?? undefined, updateProject: mockUpdateProject, }; } @@ -137,11 +129,11 @@ describe('Project Resolver - Task Manager Mutations', () => { }); const context = createMockContext(mockProject); - const result = await projectResolver.Mutation.disconnectTaskManager( + const result = (await projectResolver.Mutation.disconnectTaskManager( {}, { projectId: mockProject._id.toString() }, context - ); + )) as { taskManager: ProjectTaskManagerConfig | null }; expect(context.factories.projectsFactory.findById).toHaveBeenCalledWith(mockProject._id.toString()); expect(mockProject.updateProject).toHaveBeenCalledWith({ @@ -201,7 +193,7 @@ describe('Project Resolver - Task Manager Mutations', () => { { projectId: mockProject._id.toString() }, context ); - fail('Expected ApolloError to be thrown'); + throw new Error('Expected ApolloError to be thrown'); } catch (error) { expect(error).toBeInstanceOf(ApolloError); expect((error as ApolloError).message).toBe('Failed to disconnect Task Manager'); @@ -214,11 +206,11 @@ describe('Project Resolver - Task Manager Mutations', () => { }); const context = createMockContext(mockProject); - const result = await projectResolver.Mutation.disconnectTaskManager( + const result = (await projectResolver.Mutation.disconnectTaskManager( {}, { projectId: mockProject._id.toString() }, context - ); + )) as { taskManager: ProjectTaskManagerConfig | null }; expect(mockProject.updateProject).toHaveBeenCalledWith({ taskManager: null, @@ -255,11 +247,11 @@ describe('Project Resolver - Task Manager Mutations', () => { assignAgent: true, }; - const result = await projectResolver.Mutation.updateTaskManagerSettings( + const result = (await projectResolver.Mutation.updateTaskManagerSettings( {}, { input }, context - ); + )) as { taskManager: ProjectTaskManagerConfig | null }; expect(context.factories.projectsFactory.findById).toHaveBeenCalledWith(mockProject._id.toString()); expect(mockProject.updateProject).toHaveBeenCalledWith({ @@ -472,7 +464,7 @@ describe('Project Resolver - Task Manager Mutations', () => { { input }, context ); - fail('Expected ApolloError to be thrown'); + throw new Error('Expected ApolloError to be thrown'); } catch (error) { expect(error).toBeInstanceOf(ApolloError); expect((error as ApolloError).message).toBe('Failed to update Task Manager settings'); From 7ae7067c50ff7ca6a6c8f3cc94ff94faded3129a Mon Sep 17 00:00:00 2001 From: Peter Savchenko Date: Sun, 25 Jan 2026 22:50:39 +0300 Subject: [PATCH 15/15] Preserve null in project update for taskManager Adjusts mock project update logic to correctly preserve null values for taskManager when explicitly set, ensuring accurate test behavior for scenarios like disconnectTaskManager. Also refactors test setup in GitHub routes integration test for clarity. --- test/integrations/github-routes.test.ts | 2 +- test/resolvers/project.test.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/integrations/github-routes.test.ts b/test/integrations/github-routes.test.ts index 6a73b2f0..81094183 100644 --- a/test/integrations/github-routes.test.ts +++ b/test/integrations/github-routes.test.ts @@ -681,8 +681,8 @@ describe('GitHub Routes - /integration/github/connect', () => { workspaceId, }); mockProject.taskManager = null; + mockProject.updateProject = jest.fn().mockResolvedValue(undefined); mockGetInstallationForRepository.mockResolvedValue({}); - mockProject.updateProject.mockResolvedValue(undefined); const factories: ContextFactories = { projectsFactory: { diff --git a/test/resolvers/project.test.ts b/test/resolvers/project.test.ts index 10f940b5..bda54a40 100644 --- a/test/resolvers/project.test.ts +++ b/test/resolvers/project.test.ts @@ -51,7 +51,12 @@ function createMockProject(options: { const mockUpdateProject = jest.fn().mockImplementation(async (data) => { /** * Return updated project with new data + * Preserve null when explicitly passed (e.g. disconnectTaskManager sets taskManager: null) */ + const nextTaskManager = data.taskManager !== undefined + ? data.taskManager + : (taskManager ?? undefined); + return { _id: new ObjectId(projectId), workspaceId: new ObjectId(workspaceId), @@ -60,7 +65,7 @@ function createMockProject(options: { token: 'test-token', notifications: [], eventGroupingPatterns: [], - taskManager: data.taskManager !== undefined ? (data.taskManager ?? undefined) : (taskManager ?? undefined), + taskManager: nextTaskManager, }; });