diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0ab4a63..c99604d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Implement dynamic tab titles for files and folders in browse tab. [#560](https://github.com/sourcebot-dev/sourcebot/pull/560) +- Added support for passing db connection url as seperate `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_NAME`, and `DATABASE_ARGS` env vars. [#545](https://github.com/sourcebot-dev/sourcebot/pull/545) +- Added support for GitHub Apps for service auth. [#570](https://github.com/sourcebot-dev/sourcebot/pull/570) ### Fixed - Fixed "dubious ownership" errors when cloning / fetching repos. [#553](https://github.com/sourcebot-dev/sourcebot/pull/553) @@ -27,9 +29,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed spam "login page loaded" log. [#552](https://github.com/sourcebot-dev/sourcebot/pull/552) - Removed connections management page. [#563](https://github.com/sourcebot-dev/sourcebot/pull/563) -### Added -- Added support for passing db connection url as seperate `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_NAME`, and `DATABASE_ARGS` env vars. [#545](https://github.com/sourcebot-dev/sourcebot/pull/545) - ## [4.7.3] - 2025-09-29 ### Fixed diff --git a/docs/docs/configuration/auth/overview.mdx b/docs/docs/configuration/auth/overview.mdx index 2544c0134..725252be1 100644 --- a/docs/docs/configuration/auth/overview.mdx +++ b/docs/docs/configuration/auth/overview.mdx @@ -10,7 +10,7 @@ Sourcebot's built-in authentication system gates your deployment, and allows adm Configure additional authentication providers for your deployment. - + Learn how to configure how members join your deployment. diff --git a/docs/docs/configuration/auth/providers.mdx b/docs/docs/configuration/auth/providers.mdx index ae52ea460..7fda085af 100644 --- a/docs/docs/configuration/auth/providers.mdx +++ b/docs/docs/configuration/auth/providers.mdx @@ -33,6 +33,13 @@ The following authentication providers require an [enterprise license](/docs/lic [Auth.js GitHub Provider Docs](https://authjs.dev/getting-started/providers/github) +Authentication using both a **GitHub OAuth App** and a **GitHub App** is supported. In both cases, you must provide Sourcebot the `CLIENT_ID` and `SECRET_ID` and configure the +callback URL correctly (more info in Auth.js docs). + +When using a **GitHub App** for auth, enable the following permissions: +- `“Email addresses” account permissions (read)` +- `"Metadata" repository permissions (read)` (only needed if enabling [permission syncing](/docs/features/permission-syncing)) + **Required environment variables:** - `AUTH_EE_GITHUB_CLIENT_ID` - `AUTH_EE_GITHUB_CLIENT_SECRET` diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx index a51aeb370..b6fba9eba 100644 --- a/docs/docs/configuration/environment-variables.mdx +++ b/docs/docs/configuration/environment-variables.mdx @@ -62,9 +62,9 @@ The following environment variables allow you to configure your Sourcebot deploy ### Review Agent Environment Variables | Variable | Default | Description | | :------- | :------ | :---------- | -| `GITHUB_APP_ID` | `-` |

The GitHub App ID used for review agent authentication.

| -| `GITHUB_APP_PRIVATE_KEY_PATH` | `-` |

The container relative path to the private key file for the GitHub App used by the review agent.

| -| `GITHUB_APP_WEBHOOK_SECRET` | `-` |

The webhook secret for the GitHub App used by the review agent.

| +| `GITHUB_REVIEW_AGENT_APP_ID` | `-` |

The GitHub App ID used for review agent authentication.

| +| `GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH` | `-` |

The container relative path to the private key file for the GitHub App used by the review agent.

| +| `GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET` | `-` |

The webhook secret for the GitHub App used by the review agent.

| | `OPENAI_API_KEY` | `-` |

The OpenAI API key used by the review agent.

| | `REVIEW_AGENT_API_KEY` | `-` |

The Sourcebot API key used by the review agent.

| | `REVIEW_AGENT_AUTO_REVIEW_ENABLED` | `false` |

Enables/disables automatic code reviews by the review agent.

| diff --git a/docs/docs/features/agents/review-agent.mdx b/docs/docs/features/agents/review-agent.mdx index 743611d0d..49e1834d5 100644 --- a/docs/docs/features/agents/review-agent.mdx +++ b/docs/docs/features/agents/review-agent.mdx @@ -44,9 +44,9 @@ Before you get started, make sure you have an OpenAPI account that you can creat Sourcebot requires the following environment variables to begin reviewing PRs through your new GitHub app: - - `GITHUB_APP_ID`: The client ID of your GitHub app. Can be found in your [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings) - - `GITHUB_APP_WEBHOOK_SECRET`: The webhook secret you defined in your GitHub app. Can be found in your [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings) - - `GITHUB_APP_PRIVATE_KEY_PATH`: The path to your app's private key. If you're running Sourcebot from a container, this is the path to this file from within your container + - `GITHUB_REVIEW_AGENT_APP_ID`: The client ID of your GitHub app. Can be found in your [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings) + - `GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET`: The webhook secret you defined in your GitHub app. Can be found in your [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings) + - `GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH`: The path to your app's private key. If you're running Sourcebot from a container, this is the path to this file from within your container (ex `/data/review-agent-key.pem`). You must copy the private key file into the directory you mount to Sourcebot (similar to the config file). You can generate a private key file for your app in the [app settings](https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/quickstart#navigate-to-your-app-settings). You must copy this private key file into the @@ -74,9 +74,9 @@ Before you get started, make sure you have an OpenAPI account that you can creat - "/Users/michael/sourcebot_review_agent_workspace:/data" environment: CONFIG_PATH: "/data/config.json" - GITHUB_APP_ID: "my-github-app-id" - GITHUB_APP_WEBHOOK_SECRET: "my-github-app-webhook-secret" - GITHUB_APP_PRIVATE_KEY_PATH: "/data/review-agent-key.pem" + GITHUB_REVIEW_AGENT_APP_ID: "my-github-app-id" + GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET: "my-github-app-webhook-secret" + GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH: "/data/review-agent-key.pem" REVIEW_AGENT_API_KEY: "sourcebot-my-key" OPENAI_API_KEY: "sk-proj-my-open-api-key" ``` diff --git a/docs/snippets/schemas/v3/app.schema.mdx b/docs/snippets/schemas/v3/app.schema.mdx new file mode 100644 index 000000000..7eabc79fe --- /dev/null +++ b/docs/snippets/schemas/v3/app.schema.mdx @@ -0,0 +1,82 @@ +{/* THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! */} +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppConfig", + "oneOf": [ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "description": "The private key of the GitHub App.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": [ + "privateKey" + ] + }, + { + "required": [ + "privateKeyPath" + ] + } + ], + "additionalProperties": false + } + ] +} +``` diff --git a/docs/snippets/schemas/v3/githubApp.schema.mdx b/docs/snippets/schemas/v3/githubApp.schema.mdx new file mode 100644 index 000000000..2d1aea882 --- /dev/null +++ b/docs/snippets/schemas/v3/githubApp.schema.mdx @@ -0,0 +1,76 @@ +{/* THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! */} +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "description": "The private key of the GitHub App.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": [ + "privateKey" + ] + }, + { + "required": [ + "privateKeyPath" + ] + } + ], + "additionalProperties": false +} +``` diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 9df06a9f1..c79cd2b37 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -4273,6 +4273,89 @@ } ] } + }, + "apps": { + "type": "array", + "description": "Defines a collection of apps that are available to Sourcebot.", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppConfig", + "oneOf": [ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ], + "description": "The private key of the GitHub App." + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": [ + "privateKey" + ] + }, + { + "required": [ + "privateKeyPath" + ] + } + ], + "additionalProperties": false + } + ] + } } }, "additionalProperties": false diff --git a/packages/backend/package.json b/packages/backend/package.json index 800d2504a..8329fbbb7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -24,6 +24,7 @@ "dependencies": { "@coderabbitai/bitbucket": "^1.1.3", "@gitbeaker/rest": "^40.5.1", + "@octokit/app": "^16.1.1", "@octokit/rest": "^21.0.2", "@sentry/cli": "^2.42.2", "@sentry/node": "^9.3.0", diff --git a/packages/backend/src/ee/githubAppManager.ts b/packages/backend/src/ee/githubAppManager.ts new file mode 100644 index 000000000..2205b9394 --- /dev/null +++ b/packages/backend/src/ee/githubAppManager.ts @@ -0,0 +1,138 @@ +import { GithubAppConfig, SourcebotConfig } from "@sourcebot/schemas/v3/index.type"; +import { loadConfig } from "@sourcebot/shared"; +import { env } from "../env.js"; +import { createLogger } from "@sourcebot/logger"; +import { getTokenFromConfig } from "../utils.js"; +import { PrismaClient } from "@sourcebot/db"; +import { App } from "@octokit/app"; + +const logger = createLogger('githubAppManager'); +const GITHUB_DEFAULT_DEPLOYMENT_HOSTNAME = 'github.com'; + +type Installation = { + id: number; + appId: number; + account: { + login: string; + type: 'organization' | 'user'; + }; + createdAt: string; + expiresAt: string; + token: string; +}; + +export class GithubAppManager { + private static instance: GithubAppManager | null = null; + private octokitApps: Map; + private installationMap: Map; + private db: PrismaClient | null = null; + private initialized: boolean = false; + + private constructor() { + this.octokitApps = new Map(); + this.installationMap = new Map(); + } + + public static getInstance(): GithubAppManager { + if (!GithubAppManager.instance) { + GithubAppManager.instance = new GithubAppManager(); + } + return GithubAppManager.instance; + } + + private ensureInitialized(): void { + if (!this.initialized) { + throw new Error('GithubAppManager must be initialized before use. Call init() first.'); + } + } + + public async init(db: PrismaClient) { + this.db = db; + const config = await loadConfig(env.CONFIG_PATH!); + const githubApps = config.apps?.filter(app => app.type === 'githubApp') as GithubAppConfig[]; + + logger.info(`Found ${githubApps.length} GitHub apps in config`); + + for (const app of githubApps) { + const deploymentHostname = app.deploymentHostname as string || GITHUB_DEFAULT_DEPLOYMENT_HOSTNAME; + + // @todo: we should move SINGLE_TENANT_ORG_ID to shared package or just remove the need to pass this in + // when resolving tokens + const SINGLE_TENANT_ORG_ID = 1; + const privateKey = await getTokenFromConfig(app.privateKey, SINGLE_TENANT_ORG_ID, this.db!); + + const octokitApp = new App({ + appId: Number(app.id), + privateKey: privateKey, + }); + this.octokitApps.set(Number(app.id), octokitApp); + + const installations = await octokitApp.octokit.request("GET /app/installations"); + logger.info(`Found ${installations.data.length} GitHub App installations for ${deploymentHostname}/${app.id}:`); + + for (const installationData of installations.data) { + if (!installationData.account || !installationData.account.login || !installationData.account.type) { + logger.warn(`Skipping installation ${installationData.id}: missing account data (${installationData.account})`); + continue; + } + + logger.info(`\tInstallation ID: ${installationData.id}, Account: ${installationData.account.login}, Type: ${installationData.account.type}`); + + const owner = installationData.account.login; + const accountType = installationData.account.type.toLowerCase() as 'organization' | 'user'; + const installationOctokit = await octokitApp.getInstallationOctokit(installationData.id); + const auth = await installationOctokit.auth({ type: "installation" }) as { expires_at: string, token: string }; + + const installation: Installation = { + id: installationData.id, + appId: Number(app.id), + account: { + login: owner, + type: accountType, + }, + createdAt: installationData.created_at, + expiresAt: auth.expires_at, + token: auth.token + }; + this.installationMap.set(this.generateMapKey(owner, deploymentHostname), installation); + } + } + + this.initialized = true; + } + + public async getInstallationToken(owner: string, deploymentHostname: string = GITHUB_DEFAULT_DEPLOYMENT_HOSTNAME): Promise { + this.ensureInitialized(); + + const key = this.generateMapKey(owner, deploymentHostname); + const installation = this.installationMap.get(key) as Installation | undefined; + if (!installation) { + throw new Error(`GitHub App Installation not found for ${key}`); + } + + if (installation.expiresAt < new Date().toISOString()) { + const octokitApp = this.octokitApps.get(installation.appId) as App; + const installationOctokit = await octokitApp.getInstallationOctokit(installation.id); + const auth = await installationOctokit.auth({ type: "installation" }) as { expires_at: string, token: string }; + + const newInstallation: Installation = { + ...installation, + expiresAt: auth.expires_at, + token: auth.token + }; + this.installationMap.set(key, newInstallation); + + return newInstallation.token; + } else { + return installation.token; + } + } + + public appsConfigured() { + return this.octokitApps.size > 0; + } + + private generateMapKey(owner: string, deploymentHostname: string): string { + return `${deploymentHostname}/${owner}`; + } +} \ No newline at end of file diff --git a/packages/backend/src/ee/repoPermissionSyncer.ts b/packages/backend/src/ee/repoPermissionSyncer.ts index 453b94f6a..2393561a4 100644 --- a/packages/backend/src/ee/repoPermissionSyncer.ts +++ b/packages/backend/src/ee/repoPermissionSyncer.ts @@ -18,7 +18,6 @@ const QUEUE_NAME = 'repoPermissionSyncQueue'; const logger = createLogger('repo-permission-syncer'); - export class RepoPermissionSyncer { private queue: Queue; private worker: Worker; diff --git a/packages/backend/src/github.ts b/packages/backend/src/github.ts index 2b42eed23..19752914e 100644 --- a/packages/backend/src/github.ts +++ b/packages/backend/src/github.ts @@ -8,6 +8,8 @@ import { BackendException, BackendError } from "@sourcebot/error"; import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js"; import * as Sentry from "@sentry/node"; import { env } from "./env.js"; +import { GithubAppManager } from "./ee/githubAppManager.js"; +import { hasEntitlement } from "@sourcebot/shared"; const logger = createLogger('github'); const GITHUB_CLOUD_HOSTNAME = "github.com"; @@ -55,6 +57,40 @@ export const createOctokitFromToken = async ({ token, url }: { token?: string, u }; } +/** + * Helper function to get an authenticated Octokit instance using GitHub App if available, + * otherwise falls back to the provided octokit instance. + */ +const getOctokitWithGithubApp = async ( + octokit: Octokit, + owner: string, + url: string | undefined, + context: string +): Promise => { + if (!hasEntitlement('github-app') || !GithubAppManager.getInstance().appsConfigured()) { + return octokit; + } + + try { + const hostname = url ? new URL(url).hostname : GITHUB_CLOUD_HOSTNAME; + const token = await GithubAppManager.getInstance().getInstallationToken(owner, hostname); + const { octokit: octokitFromToken, isAuthenticated } = await createOctokitFromToken({ + token, + url, + }); + + if (isAuthenticated) { + return octokitFromToken; + } else { + logger.error(`Failed to authenticate with GitHub App for ${context}. Falling back to legacy token resolution.`); + return octokit; + } + } catch (error) { + logger.error(`Error getting GitHub App token for ${context}. Falling back to legacy token resolution.`, error); + return octokit; + } +} + export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, orgId: number, db: PrismaClient, signal: AbortSignal) => { const hostname = config.url ? new URL(config.url).hostname : @@ -107,19 +143,19 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o }; if (config.orgs) { - const { validRepos, notFoundOrgs } = await getReposForOrgs(config.orgs, octokit, signal); + const { validRepos, notFoundOrgs } = await getReposForOrgs(config.orgs, octokit, signal, config.url); allRepos = allRepos.concat(validRepos); notFound.orgs = notFoundOrgs; } if (config.repos) { - const { validRepos, notFoundRepos } = await getRepos(config.repos, octokit, signal); + const { validRepos, notFoundRepos } = await getRepos(config.repos, octokit, signal, config.url); allRepos = allRepos.concat(validRepos); notFound.repos = notFoundRepos; } if (config.users) { - const { validRepos, notFoundUsers } = await getReposOwnedByUsers(config.users, octokit, signal); + const { validRepos, notFoundUsers } = await getReposOwnedByUsers(config.users, octokit, signal, config.url); allRepos = allRepos.concat(validRepos); notFound.users = notFoundUsers; } @@ -178,11 +214,12 @@ export const getReposForAuthenticatedUser = async (visibility: 'all' | 'private' } } -const getReposOwnedByUsers = async (users: string[], octokit: Octokit, signal: AbortSignal) => { +const getReposOwnedByUsers = async (users: string[], octokit: Octokit, signal: AbortSignal, url?: string) => { const results = await Promise.allSettled(users.map(async (user) => { try { logger.debug(`Fetching repository info for user ${user}...`); + const octokitToUse = await getOctokitWithGithubApp(octokit, user, url, `user ${user}`); const { durationMs, data } = await measure(async () => { const fetchFn = async () => { let query = `user:${user}`; @@ -194,7 +231,7 @@ const getReposOwnedByUsers = async (users: string[], octokit: Octokit, signal: A // the username as a parameter. // @see: https://github.com/orgs/community/discussions/24382#discussioncomment-3243958 // @see: https://api.github.com/search/repositories?q=user:USERNAME - const searchResults = await octokit.paginate(octokit.rest.search.repos, { + const searchResults = await octokitToUse.paginate(octokitToUse.rest.search.repos, { q: query, per_page: 100, request: { @@ -237,13 +274,14 @@ const getReposOwnedByUsers = async (users: string[], octokit: Octokit, signal: A }; } -const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSignal) => { +const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSignal, url?: string) => { const results = await Promise.allSettled(orgs.map(async (org) => { try { logger.info(`Fetching repository info for org ${org}...`); + const octokitToUse = await getOctokitWithGithubApp(octokit, org, url, `org ${org}`); const { durationMs, data } = await measure(async () => { - const fetchFn = () => octokit.paginate(octokit.repos.listForOrg, { + const fetchFn = () => octokitToUse.paginate(octokitToUse.repos.listForOrg, { org: org, per_page: 100, request: { @@ -283,14 +321,15 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi }; } -const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSignal) => { +const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSignal, url?: string) => { const results = await Promise.allSettled(repoList.map(async (repo) => { try { const [owner, repoName] = repo.split('/'); logger.info(`Fetching repository info for ${repo}...`); + const octokitToUse = await getOctokitWithGithubApp(octokit, owner, url, `repo ${repo}`); const { durationMs, data: result } = await measure(async () => { - const fetchFn = () => octokit.repos.get({ + const fetchFn = () => octokitToUse.repos.get({ owner, repo: repoName, request: { diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 78a1ce6c6..354e61ff7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -10,6 +10,7 @@ import { ConnectionManager } from './connectionManager.js'; import { DEFAULT_SETTINGS, INDEX_CACHE_DIR, REPOS_CACHE_DIR } from './constants.js'; import { RepoPermissionSyncer } from './ee/repoPermissionSyncer.js'; import { UserPermissionSyncer } from "./ee/userPermissionSyncer.js"; +import { GithubAppManager } from "./ee/githubAppManager.js"; import { env } from "./env.js"; import { RepoIndexManager } from "./repoIndexManager.js"; import { PromClient } from './promClient.js'; @@ -58,6 +59,11 @@ const promClient = new PromClient(); const settings = await getSettings(env.CONFIG_PATH); + +if (hasEntitlement('github-app')) { + await GithubAppManager.getInstance().init(prisma); +} + const connectionManager = new ConnectionManager(prisma, settings, redis); const repoPermissionSyncer = new RepoPermissionSyncer(prisma, settings, redis); const userPermissionSyncer = new UserPermissionSyncer(prisma, settings, redis); diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index aaaad4eaf..01b44f54e 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -6,6 +6,8 @@ import { getTokenFromConfig as getTokenFromConfigBase } from "@sourcebot/crypto" import { BackendException, BackendError } from "@sourcebot/error"; import * as Sentry from "@sentry/node"; import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig, BitbucketConnectionConfig, AzureDevOpsConnectionConfig } from '@sourcebot/schemas/v3/connection.type'; +import { GithubAppManager } from "./ee/githubAppManager.js"; +import { hasEntitlement } from "@sourcebot/shared"; import { REPOS_CACHE_DIR } from "./constants.js"; export const measure = async (cb: () => Promise) => { @@ -126,6 +128,28 @@ export const fetchWithRetry = async ( // may have their own token. This method will just pick the first connection that has a token (if one exists) and uses that. This // may technically cause syncing to fail if that connection's token just so happens to not have access to the repo it's referencing. export const getAuthCredentialsForRepo = async (repo: RepoWithConnections, db: PrismaClient, logger?: Logger): Promise => { + // If we have github apps configured we assume that we must use them for github service auth + if (repo.external_codeHostType === 'github' && hasEntitlement('github-app') && GithubAppManager.getInstance().appsConfigured()) { + const owner = repo.displayName?.split('/')[0]; + const deploymentHostname = new URL(repo.external_codeHostUrl).hostname; + if (!owner || !deploymentHostname) { + throw new Error(`Failed to fetch GitHub App for repo ${repo.displayName}:Invalid repo displayName (${repo.displayName}) or deployment hostname (${deploymentHostname})`); + } + + const token = await GithubAppManager.getInstance().getInstallationToken(owner, deploymentHostname); + return { + hostUrl: repo.external_codeHostUrl, + token, + cloneUrlWithToken: createGitCloneUrlWithToken( + repo.cloneUrl, + { + username: 'x-access-token', + password: token + } + ), + } + } + for (const { connection } of repo.connections) { if (connection.connectionType === 'github') { const config = connection.config as unknown as GithubConnectionConfig; diff --git a/packages/schemas/src/v3/app.schema.ts b/packages/schemas/src/v3/app.schema.ts new file mode 100644 index 000000000..5b4b96f75 --- /dev/null +++ b/packages/schemas/src/v3/app.schema.ts @@ -0,0 +1,81 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! +const schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppConfig", + "oneOf": [ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "description": "The private key of the GitHub App.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": [ + "privateKey" + ] + }, + { + "required": [ + "privateKeyPath" + ] + } + ], + "additionalProperties": false + } + ] +} as const; +export { schema as appSchema }; \ No newline at end of file diff --git a/packages/schemas/src/v3/app.type.ts b/packages/schemas/src/v3/app.type.ts new file mode 100644 index 000000000..255ef033d --- /dev/null +++ b/packages/schemas/src/v3/app.type.ts @@ -0,0 +1,6 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! + +export type AppConfig = GithubAppConfig; +export type GithubAppConfig = { + [k: string]: unknown; +}; diff --git a/packages/schemas/src/v3/githubApp.schema.ts b/packages/schemas/src/v3/githubApp.schema.ts new file mode 100644 index 000000000..aab0ef20a --- /dev/null +++ b/packages/schemas/src/v3/githubApp.schema.ts @@ -0,0 +1,75 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! +const schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "description": "The private key of the GitHub App.", + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": [ + "privateKey" + ] + }, + { + "required": [ + "privateKeyPath" + ] + } + ], + "additionalProperties": false +} as const; +export { schema as githubAppSchema }; \ No newline at end of file diff --git a/packages/schemas/src/v3/githubApp.type.ts b/packages/schemas/src/v3/githubApp.type.ts new file mode 100644 index 000000000..cd5af6af1 --- /dev/null +++ b/packages/schemas/src/v3/githubApp.type.ts @@ -0,0 +1,34 @@ +// THIS IS A AUTO-GENERATED FILE. DO NOT MODIFY MANUALLY! + +export type GithubAppConfig = { + /** + * GitHub App Configuration + */ + type: "githubApp"; + /** + * The hostname of the GitHub App deployment. + */ + deploymentHostname?: string; + /** + * The ID of the GitHub App. + */ + id: string; + /** + * The private key of the GitHub App. + */ + privateKey?: + | { + /** + * The name of the secret that contains the token. + */ + secret: string; + } + | { + /** + * The name of the environment variable that contains the token. Only supported in declarative connection configs. + */ + env: string; + }; +} & { + [k: string]: unknown; +}; diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index 38ec2f0a0..c326e7b65 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -4272,6 +4272,89 @@ const schema = { } ] } + }, + "apps": { + "type": "array", + "description": "Defines a collection of apps that are available to Sourcebot.", + "items": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppConfig", + "oneOf": [ + { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "anyOf": [ + { + "type": "object", + "properties": { + "secret": { + "type": "string", + "description": "The name of the secret that contains the token." + } + }, + "required": [ + "secret" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "env": { + "type": "string", + "description": "The name of the environment variable that contains the token. Only supported in declarative connection configs." + } + }, + "required": [ + "env" + ], + "additionalProperties": false + } + ], + "description": "The private key of the GitHub App." + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": [ + "privateKey" + ] + }, + { + "required": [ + "privateKeyPath" + ] + } + ], + "additionalProperties": false + } + ] + } } }, "additionalProperties": false diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index 4e8982dc0..3d0b19478 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -25,6 +25,10 @@ export type LanguageModel = | OpenAICompatibleLanguageModel | OpenRouterLanguageModel | XaiLanguageModel; +export type AppConfig = GithubAppConfig; +export type GithubAppConfig = { + [k: string]: unknown; +}; export interface SourcebotConfig { $schema?: string; @@ -45,6 +49,10 @@ export interface SourcebotConfig { * Defines a collection of language models that are available to Sourcebot. */ models?: LanguageModel[]; + /** + * Defines a collection of apps that are available to Sourcebot. + */ + apps?: AppConfig[]; } /** * Defines the global settings for Sourcebot. diff --git a/packages/shared/src/entitlements.ts b/packages/shared/src/entitlements.ts index be40b9275..1c0c688ff 100644 --- a/packages/shared/src/entitlements.ts +++ b/packages/shared/src/entitlements.ts @@ -39,15 +39,16 @@ const entitlements = [ "code-nav", "audit", "analytics", - "permission-syncing" + "permission-syncing", + "github-app" ] as const; export type Entitlement = (typeof entitlements)[number]; const entitlementsByPlan: Record = { oss: ["anonymous-access"], "cloud:team": ["billing", "multi-tenancy", "sso", "code-nav"], - "self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit", "analytics", "permission-syncing"], - "self-hosted:enterprise-unlimited": ["search-contexts", "anonymous-access", "sso", "code-nav", "audit", "analytics", "permission-syncing"], + "self-hosted:enterprise": ["search-contexts", "sso", "code-nav", "audit", "analytics", "permission-syncing", "github-app"], + "self-hosted:enterprise-unlimited": ["search-contexts", "anonymous-access", "sso", "code-nav", "audit", "analytics", "permission-syncing", "github-app"], // Special entitlement for https://demo.sourcebot.dev "cloud:demo": ["anonymous-access", "code-nav", "search-contexts"], } as const; diff --git a/packages/web/src/app/[domain]/agents/page.tsx b/packages/web/src/app/[domain]/agents/page.tsx index 03b2a2d69..1da98ff20 100644 --- a/packages/web/src/app/[domain]/agents/page.tsx +++ b/packages/web/src/app/[domain]/agents/page.tsx @@ -8,7 +8,7 @@ const agents = [ id: "review-agent", name: "Review Agent", description: "An AI code review agent that reviews your PRs. Uses the code indexed on Sourcebot to provide codebase-wide context.", - requiredEnvVars: ["GITHUB_APP_ID", "GITHUB_APP_WEBHOOK_SECRET", "GITHUB_APP_PRIVATE_KEY_PATH", "OPENAI_API_KEY"], + requiredEnvVars: ["GITHUB_REVIEW_AGENT_APP_ID", "GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET", "GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH", "OPENAI_API_KEY"], configureUrl: "https://docs.sourcebot.dev/docs/features/agents/review-agent" }, ]; diff --git a/packages/web/src/app/[domain]/components/settingsDropdown.tsx b/packages/web/src/app/[domain]/components/settingsDropdown.tsx index 377d0ef79..92a5705aa 100644 --- a/packages/web/src/app/[domain]/components/settingsDropdown.tsx +++ b/packages/web/src/app/[domain]/components/settingsDropdown.tsx @@ -73,19 +73,24 @@ export const SettingsDropdown = ({ - + {session?.user ? ( -
- +
+ - - {session.user.name && session.user.name.length > 0 ? session.user.name[0] : 'U'} + + {session.user.name && session.user.name.length > 0 ? session.user.name[0].toUpperCase() : 'U'} -

{session.user.email ?? "User"}

+
+

{session.user.name ?? "User"}

+ {session.user.email && ( +

{session.user.email}

+ )} +
{ diff --git a/packages/web/src/app/api/(server)/webhook/route.ts b/packages/web/src/app/api/(server)/webhook/route.ts index ee9d4dcc1..ade6e54d0 100644 --- a/packages/web/src/app/api/(server)/webhook/route.ts +++ b/packages/web/src/app/api/(server)/webhook/route.ts @@ -14,16 +14,16 @@ import { createLogger } from "@sourcebot/logger"; const logger = createLogger('github-webhook'); let githubApp: App | undefined; -if (env.GITHUB_APP_ID && env.GITHUB_APP_WEBHOOK_SECRET && env.GITHUB_APP_PRIVATE_KEY_PATH) { +if (env.GITHUB_REVIEW_AGENT_APP_ID && env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET && env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH) { try { - const privateKey = fs.readFileSync(env.GITHUB_APP_PRIVATE_KEY_PATH, "utf8"); + const privateKey = fs.readFileSync(env.GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH, "utf8"); const throttledOctokit = Octokit.plugin(throttling); githubApp = new App({ - appId: env.GITHUB_APP_ID, + appId: env.GITHUB_REVIEW_AGENT_APP_ID, privateKey: privateKey, webhooks: { - secret: env.GITHUB_APP_WEBHOOK_SECRET, + secret: env.GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET, }, Octokit: throttledOctokit, throttle: { diff --git a/packages/web/src/ee/features/sso/sso.ts b/packages/web/src/ee/features/sso/sso.ts index 0f14a364f..287453d10 100644 --- a/packages/web/src/ee/features/sso/sso.ts +++ b/packages/web/src/ee/features/sso/sso.ts @@ -20,13 +20,13 @@ export const getSSOProviders = (): Provider[] => { const providers: Provider[] = []; if (env.AUTH_EE_GITHUB_CLIENT_ID && env.AUTH_EE_GITHUB_CLIENT_SECRET) { - const baseUrl = env.AUTH_EE_GITHUB_BASE_URL ?? "https://github.com"; - const apiUrl = env.AUTH_EE_GITHUB_BASE_URL ? `${env.AUTH_EE_GITHUB_BASE_URL}/api/v3` : "https://api.github.com"; providers.push(GitHub({ clientId: env.AUTH_EE_GITHUB_CLIENT_ID, clientSecret: env.AUTH_EE_GITHUB_CLIENT_SECRET, + enterprise: { + baseUrl: env.AUTH_EE_GITHUB_BASE_URL, + }, authorization: { - url: `${baseUrl}/login/oauth/authorize`, params: { scope: [ 'read:user', @@ -41,12 +41,6 @@ export const getSSOProviders = (): Provider[] => { ].join(' '), }, }, - token: { - url: `${baseUrl}/login/oauth/access_token`, - }, - userinfo: { - url: `${apiUrl}/user`, - }, })); } diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs index f515c3d91..31b3d97d9 100644 --- a/packages/web/src/env.mjs +++ b/packages/web/src/env.mjs @@ -85,9 +85,9 @@ export const env = createEnv({ SOURCEBOT_EE_AUDIT_LOGGING_ENABLED: booleanSchema.default('true'), // GitHub app for review agent - GITHUB_APP_ID: z.string().optional(), - GITHUB_APP_WEBHOOK_SECRET: z.string().optional(), - GITHUB_APP_PRIVATE_KEY_PATH: z.string().optional(), + GITHUB_REVIEW_AGENT_APP_ID: z.string().optional(), + GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET: z.string().optional(), + GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH: z.string().optional(), REVIEW_AGENT_API_KEY: z.string().optional(), REVIEW_AGENT_LOGGING_ENABLED: booleanSchema.default('true'), REVIEW_AGENT_AUTO_REVIEW_ENABLED: booleanSchema.default('false'), diff --git a/packages/web/src/features/search/searchApi.ts b/packages/web/src/features/search/searchApi.ts index 3c7ea3732..35df48486 100644 --- a/packages/web/src/features/search/searchApi.ts +++ b/packages/web/src/features/search/searchApi.ts @@ -1,6 +1,5 @@ 'use server'; -import { env } from "@/env.mjs"; import { invalidZoektResponse, ServiceError } from "../../lib/serviceError"; import { isServiceError } from "../../lib/utils"; import { zoektFetch } from "./zoektClient"; diff --git a/schemas/v3/app.json b/schemas/v3/app.json new file mode 100644 index 000000000..007033269 --- /dev/null +++ b/schemas/v3/app.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppConfig", + "oneOf": [ + { + "$ref": "./githubApp.json" + } + ] +} \ No newline at end of file diff --git a/schemas/v3/githubApp.json b/schemas/v3/githubApp.json new file mode 100644 index 000000000..c83553ced --- /dev/null +++ b/schemas/v3/githubApp.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "title": "GithubAppConfig", + "properties": { + "type": { + "const": "githubApp", + "description": "GitHub App Configuration" + }, + "deploymentHostname": { + "type": "string", + "format": "hostname", + "default": "github.com", + "description": "The hostname of the GitHub App deployment.", + "examples": [ + "github.com", + "github.example.com" + ] + }, + "id": { + "type": "string", + "description": "The ID of the GitHub App." + }, + "privateKey": { + "$ref": "./shared.json#/definitions/Token", + "description": "The private key of the GitHub App." + } + }, + "required": [ + "type", + "id" + ], + "oneOf": [ + { + "required": ["privateKey"] + }, + { + "required": ["privateKeyPath"] + } + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/schemas/v3/index.json b/schemas/v3/index.json index b697e619d..3bb129b1c 100644 --- a/schemas/v3/index.json +++ b/schemas/v3/index.json @@ -118,6 +118,13 @@ "items": { "$ref": "./languageModel.json" } + }, + "apps": { + "type": "array", + "description": "Defines a collection of apps that are available to Sourcebot.", + "items": { + "$ref": "./app.json" + } } }, "additionalProperties": false diff --git a/yarn.lock b/yarn.lock index 8cffa0aaa..34e6c1b22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3412,6 +3412,21 @@ __metadata: languageName: node linkType: hard +"@octokit/app@npm:^16.1.1": + version: 16.1.1 + resolution: "@octokit/app@npm:16.1.1" + dependencies: + "@octokit/auth-app": "npm:^8.1.1" + "@octokit/auth-unauthenticated": "npm:^7.0.2" + "@octokit/core": "npm:^7.0.5" + "@octokit/oauth-app": "npm:^8.0.2" + "@octokit/plugin-paginate-rest": "npm:^13.2.0" + "@octokit/types": "npm:^15.0.0" + "@octokit/webhooks": "npm:^14.0.0" + checksum: 10c0/1989da2151db879e85d0dbe7f1fdfff613a0d00b65eb58b8c08617186726ea9c64061639e6c630d6840772f084468d7cdfd48c001fc7f91313b67cf58a827d81 + languageName: node + linkType: hard + "@octokit/auth-app@npm:^7.2.1": version: 7.2.1 resolution: "@octokit/auth-app@npm:7.2.1" @@ -3428,6 +3443,22 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-app@npm:^8.1.1": + version: 8.1.1 + resolution: "@octokit/auth-app@npm:8.1.1" + dependencies: + "@octokit/auth-oauth-app": "npm:^9.0.2" + "@octokit/auth-oauth-user": "npm:^6.0.1" + "@octokit/request": "npm:^10.0.5" + "@octokit/request-error": "npm:^7.0.1" + "@octokit/types": "npm:^15.0.0" + toad-cache: "npm:^3.7.0" + universal-github-app-jwt: "npm:^2.2.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/f33d12a5e7339135d013f10ae6e5f6f6f9b94af860df6b300eb158f27c445e626fd08e2d2d8eb39cdab1391918ed244a1e041ebf3c470074f42b8573c52d1dc7 + languageName: node + linkType: hard + "@octokit/auth-oauth-app@npm:^8.1.3, @octokit/auth-oauth-app@npm:^8.1.4": version: 8.1.4 resolution: "@octokit/auth-oauth-app@npm:8.1.4" @@ -3441,6 +3472,19 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-oauth-app@npm:^9.0.2": + version: 9.0.2 + resolution: "@octokit/auth-oauth-app@npm:9.0.2" + dependencies: + "@octokit/auth-oauth-device": "npm:^8.0.2" + "@octokit/auth-oauth-user": "npm:^6.0.1" + "@octokit/request": "npm:^10.0.5" + "@octokit/types": "npm:^15.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/85f4dc9caad7fa0b963ac33452ad6af3488c0b4b07d2b39b624f1bb8eec4bc5ccf3bfdd379db133439afd437d1389bd4674ba66911d685ebb5cc31c713f04594 + languageName: node + linkType: hard + "@octokit/auth-oauth-device@npm:^7.1.5": version: 7.1.5 resolution: "@octokit/auth-oauth-device@npm:7.1.5" @@ -3453,6 +3497,18 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-oauth-device@npm:^8.0.2": + version: 8.0.2 + resolution: "@octokit/auth-oauth-device@npm:8.0.2" + dependencies: + "@octokit/oauth-methods": "npm:^6.0.1" + "@octokit/request": "npm:^10.0.5" + "@octokit/types": "npm:^15.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/b69b2bda7139b4c49c7ac70f86da53317fcf49e6562fe734ecebef2ce5c8e8f973bf00e6b29e0a45cd88346fc335e73d70c53920b438e73f165590f4fb58864f + languageName: node + linkType: hard + "@octokit/auth-oauth-user@npm:^5.1.3, @octokit/auth-oauth-user@npm:^5.1.4": version: 5.1.4 resolution: "@octokit/auth-oauth-user@npm:5.1.4" @@ -3466,6 +3522,19 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-oauth-user@npm:^6.0.1": + version: 6.0.1 + resolution: "@octokit/auth-oauth-user@npm:6.0.1" + dependencies: + "@octokit/auth-oauth-device": "npm:^8.0.2" + "@octokit/oauth-methods": "npm:^6.0.1" + "@octokit/request": "npm:^10.0.5" + "@octokit/types": "npm:^15.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/8e4c8172be454a46eab05ac5ae1c6cc2984ffee7378f9196d441a53cdba9b722b8f408b9dd4b42fe736cda4144d7a22d2e026888d2ff14c8865e020cb760379a + languageName: node + linkType: hard + "@octokit/auth-token@npm:^5.0.0": version: 5.1.2 resolution: "@octokit/auth-token@npm:5.1.2" @@ -3473,6 +3542,13 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^6.0.0": + version: 6.0.0 + resolution: "@octokit/auth-token@npm:6.0.0" + checksum: 10c0/32ecc904c5f6f4e5d090bfcc679d70318690c0a0b5040cd9a25811ad9dcd44c33f2cf96b6dbee1cd56cf58fde28fb1819c01b58718aa5c971f79c822357cb5c0 + languageName: node + linkType: hard + "@octokit/auth-unauthenticated@npm:^6.1.2, @octokit/auth-unauthenticated@npm:^6.1.3": version: 6.1.3 resolution: "@octokit/auth-unauthenticated@npm:6.1.3" @@ -3483,6 +3559,16 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-unauthenticated@npm:^7.0.2": + version: 7.0.2 + resolution: "@octokit/auth-unauthenticated@npm:7.0.2" + dependencies: + "@octokit/request-error": "npm:^7.0.1" + "@octokit/types": "npm:^15.0.0" + checksum: 10c0/07a05683c0451232d1d952e725ad3a4d50dfe5487452f93cfe54b99220b46a5e94a04084b7954738f9d8437f91afce3ab535bc67fa787f711452063baf5f6821 + languageName: node + linkType: hard + "@octokit/core@npm:^6.1.4": version: 6.1.4 resolution: "@octokit/core@npm:6.1.4" @@ -3513,6 +3599,21 @@ __metadata: languageName: node linkType: hard +"@octokit/core@npm:^7.0.5": + version: 7.0.5 + resolution: "@octokit/core@npm:7.0.5" + dependencies: + "@octokit/auth-token": "npm:^6.0.0" + "@octokit/graphql": "npm:^9.0.2" + "@octokit/request": "npm:^10.0.4" + "@octokit/request-error": "npm:^7.0.1" + "@octokit/types": "npm:^15.0.0" + before-after-hook: "npm:^4.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/09aeba5f9a6b58c4e7cdd59d883a1b787bc32b17fee3b6c73af47e9b8510dc1aa6e2399274e36106ca27485d4e7b2ffda28af306ad4819fa96cd90caecf15ae7 + languageName: node + linkType: hard + "@octokit/endpoint@npm:^10.1.3": version: 10.1.3 resolution: "@octokit/endpoint@npm:10.1.3" @@ -3533,6 +3634,16 @@ __metadata: languageName: node linkType: hard +"@octokit/endpoint@npm:^11.0.1": + version: 11.0.1 + resolution: "@octokit/endpoint@npm:11.0.1" + dependencies: + "@octokit/types": "npm:^15.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10c0/a445c42a4cef357f7a181ac1dc5970db7d6c3bb36c81e10dd4032020873d4ec97402f08ebfa6ea747de8edd255ccf19a57cbb66dc4a05e5cff8c0445e29cd73d + languageName: node + linkType: hard + "@octokit/graphql@npm:^8.1.2": version: 8.2.1 resolution: "@octokit/graphql@npm:8.2.1" @@ -3555,6 +3666,17 @@ __metadata: languageName: node linkType: hard +"@octokit/graphql@npm:^9.0.2": + version: 9.0.2 + resolution: "@octokit/graphql@npm:9.0.2" + dependencies: + "@octokit/request": "npm:^10.0.4" + "@octokit/types": "npm:^15.0.0" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/aaba3de627475ac2be24d676be643c85bec089b1d9ef2c3a678fab03a525c0fd9b6c61622d190e84447ecb6aa9271882f8bcce5c278221337fd4be68d36acf10 + languageName: node + linkType: hard + "@octokit/oauth-app@npm:^7.1.6": version: 7.1.6 resolution: "@octokit/oauth-app@npm:7.1.6" @@ -3571,6 +3693,22 @@ __metadata: languageName: node linkType: hard +"@octokit/oauth-app@npm:^8.0.2": + version: 8.0.3 + resolution: "@octokit/oauth-app@npm:8.0.3" + dependencies: + "@octokit/auth-oauth-app": "npm:^9.0.2" + "@octokit/auth-oauth-user": "npm:^6.0.1" + "@octokit/auth-unauthenticated": "npm:^7.0.2" + "@octokit/core": "npm:^7.0.5" + "@octokit/oauth-authorization-url": "npm:^8.0.0" + "@octokit/oauth-methods": "npm:^6.0.1" + "@types/aws-lambda": "npm:^8.10.83" + universal-user-agent: "npm:^7.0.0" + checksum: 10c0/7bb064bfe21a6db45b57f8f5e5ecd533e802c0073d293b1c75abf264d9bc0fc952492757ea5af516207af4ec781cf793cb37e8cbbb3a8df691592bc25c364644 + languageName: node + linkType: hard + "@octokit/oauth-authorization-url@npm:^7.0.0, @octokit/oauth-authorization-url@npm:^7.1.1": version: 7.1.1 resolution: "@octokit/oauth-authorization-url@npm:7.1.1" @@ -3578,6 +3716,13 @@ __metadata: languageName: node linkType: hard +"@octokit/oauth-authorization-url@npm:^8.0.0": + version: 8.0.0 + resolution: "@octokit/oauth-authorization-url@npm:8.0.0" + checksum: 10c0/ab4964bebd8d076f945a2f3210a8a0a221a408362569d9fc2f49875ad06e594365f5fd871dac08d820793f687bff50237f7acf40d9d39c5f9de7575b6f4bad93 + languageName: node + linkType: hard + "@octokit/oauth-methods@npm:^5.1.4, @octokit/oauth-methods@npm:^5.1.5": version: 5.1.5 resolution: "@octokit/oauth-methods@npm:5.1.5" @@ -3590,6 +3735,18 @@ __metadata: languageName: node linkType: hard +"@octokit/oauth-methods@npm:^6.0.1": + version: 6.0.1 + resolution: "@octokit/oauth-methods@npm:6.0.1" + dependencies: + "@octokit/oauth-authorization-url": "npm:^8.0.0" + "@octokit/request": "npm:^10.0.5" + "@octokit/request-error": "npm:^7.0.1" + "@octokit/types": "npm:^15.0.0" + checksum: 10c0/b9aa6458f8ffdb067552cdc7783038a075401b9b3875c970ca23a29157f75573a18240b1327fb732be30468857abea41761de1f28a50a7c549ff0ced24c9d499 + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^24.2.0": version: 24.2.0 resolution: "@octokit/openapi-types@npm:24.2.0" @@ -3604,6 +3761,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^26.0.0": + version: 26.0.0 + resolution: "@octokit/openapi-types@npm:26.0.0" + checksum: 10c0/671f12c1db70b4bc8c719ec7aa10de034925f4326db0fff22837afcc0b41fd1c015d164673ef5603c5ac787a430c514b821852bfbe6f06edc4a41ad3de342e94 + languageName: node + linkType: hard + "@octokit/openapi-webhooks-types@npm:11.0.0": version: 11.0.0 resolution: "@octokit/openapi-webhooks-types@npm:11.0.0" @@ -3611,6 +3775,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-webhooks-types@npm:12.0.3": + version: 12.0.3 + resolution: "@octokit/openapi-webhooks-types@npm:12.0.3" + checksum: 10c0/1c2429bd939edcf6a6e8db7240a2138a897cd7d6a0922ac083d8ed58868866b036b90888eb22d1b952df75d5741e2df1aa02d38f182c5be83394dc30b24d657d + languageName: node + linkType: hard + "@octokit/plugin-paginate-graphql@npm:^5.2.4": version: 5.2.4 resolution: "@octokit/plugin-paginate-graphql@npm:5.2.4" @@ -3642,6 +3813,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-paginate-rest@npm:^13.2.0": + version: 13.2.1 + resolution: "@octokit/plugin-paginate-rest@npm:13.2.1" + dependencies: + "@octokit/types": "npm:^15.0.1" + peerDependencies: + "@octokit/core": ">=6" + checksum: 10c0/16cd034ee6426f742514d0ca553a2c4355cd68c2eb9211030f3ec2538f4c833d587b3737bb720e34f98be8fae15acb07693d17314350cf067557abb4cb1598fb + languageName: node + linkType: hard + "@octokit/plugin-request-log@npm:^5.3.1": version: 5.3.1 resolution: "@octokit/plugin-request-log@npm:5.3.1" @@ -3716,6 +3898,28 @@ __metadata: languageName: node linkType: hard +"@octokit/request-error@npm:^7.0.0, @octokit/request-error@npm:^7.0.1": + version: 7.0.1 + resolution: "@octokit/request-error@npm:7.0.1" + dependencies: + "@octokit/types": "npm:^15.0.0" + checksum: 10c0/c3f29db87a8d59b8217cbda8cb32be4a553de21ab08bac7ec5909e7c4a4934a32a07575547049fb11a07f0eeec45d0ae5c38295995445adda4ae17b2c66cba85 + languageName: node + linkType: hard + +"@octokit/request@npm:^10.0.4, @octokit/request@npm:^10.0.5": + version: 10.0.5 + resolution: "@octokit/request@npm:10.0.5" + dependencies: + "@octokit/endpoint": "npm:^11.0.1" + "@octokit/request-error": "npm:^7.0.1" + "@octokit/types": "npm:^15.0.0" + fast-content-type-parse: "npm:^3.0.0" + universal-user-agent: "npm:^7.0.2" + checksum: 10c0/66b607ec97280ce2a857826b7c862a48d81fdafe97c7b6b527ce7bf83b0f6eb706ce3df44eafb57c7ed0ee0b5f255db1c1471ed6d9152b8932e6e88feb845bba + languageName: node + linkType: hard + "@octokit/request@npm:^9.2.1, @octokit/request@npm:^9.2.2": version: 9.2.2 resolution: "@octokit/request@npm:9.2.2" @@ -3772,6 +3976,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^15.0.0, @octokit/types@npm:^15.0.1": + version: 15.0.1 + resolution: "@octokit/types@npm:15.0.1" + dependencies: + "@octokit/openapi-types": "npm:^26.0.0" + checksum: 10c0/f1f8d8a988c6295d669461082936a4e27d5a021ff870ebb93b8afa8f227f6eb0fb520f98631af31fc56dea0cb84e15df65e736f408cde321693154e4432c575d + languageName: node + linkType: hard + "@octokit/webhooks-methods@npm:^5.1.1": version: 5.1.1 resolution: "@octokit/webhooks-methods@npm:5.1.1" @@ -3779,6 +3992,13 @@ __metadata: languageName: node linkType: hard +"@octokit/webhooks-methods@npm:^6.0.0": + version: 6.0.0 + resolution: "@octokit/webhooks-methods@npm:6.0.0" + checksum: 10c0/7f10740e838d65c78e859bb041499cca69df7831e9f633ee70a46ca8e53d0844f2c84500df204453d171c8c3c0f8eb8b68716ee1d5c95e3cf5d09690f32e13e1 + languageName: node + linkType: hard + "@octokit/webhooks@npm:^13.6.1": version: 13.8.2 resolution: "@octokit/webhooks@npm:13.8.2" @@ -3790,6 +4010,17 @@ __metadata: languageName: node linkType: hard +"@octokit/webhooks@npm:^14.0.0": + version: 14.1.3 + resolution: "@octokit/webhooks@npm:14.1.3" + dependencies: + "@octokit/openapi-webhooks-types": "npm:12.0.3" + "@octokit/request-error": "npm:^7.0.0" + "@octokit/webhooks-methods": "npm:^6.0.0" + checksum: 10c0/7f423700784cb769f15303353154e841f66e031289a25ce44852883121ad9752f0b9ea06bde1388ff38310f64208b6de748e2c24ccd9f3021f708e5e9c6ecfac + languageName: node + linkType: hard + "@openrouter/ai-sdk-provider@npm:^1.1.0": version: 1.1.0 resolution: "@openrouter/ai-sdk-provider@npm:1.1.0" @@ -7546,6 +7777,7 @@ __metadata: dependencies: "@coderabbitai/bitbucket": "npm:^1.1.3" "@gitbeaker/rest": "npm:^40.5.1" + "@octokit/app": "npm:^16.1.1" "@octokit/rest": "npm:^21.0.2" "@sentry/cli": "npm:^2.42.2" "@sentry/node": "npm:^9.3.0" @@ -9607,6 +9839,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^4.0.0": + version: 4.0.0 + resolution: "before-after-hook@npm:4.0.0" + checksum: 10c0/9f8ae8d1b06142bcfb9ef6625226b5e50348bb11210f266660eddcf9734e0db6f9afc4cb48397ee3f5ac0a3728f3ae401cdeea88413f7bed748a71db84657be2 + languageName: node + linkType: hard + "best-effort-json-parser@npm:^1.1.2": version: 1.1.3 resolution: "best-effort-json-parser@npm:1.1.3" @@ -12316,6 +12555,13 @@ __metadata: languageName: node linkType: hard +"fast-content-type-parse@npm:^3.0.0": + version: 3.0.0 + resolution: "fast-content-type-parse@npm:3.0.0" + checksum: 10c0/06251880c83b7118af3a5e66e8bcee60d44f48b39396fc60acc2b4630bd5f3e77552b999b5c8e943d45a818854360e5e97164c374ec4b562b4df96a2cdf2e188 + languageName: node + linkType: hard + "fast-copy@npm:^3.0.2": version: 3.0.2 resolution: "fast-copy@npm:3.0.2"