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"