Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,8 @@ ARG NEXT_PUBLIC_SOURCEBOT_TELEMETRY_DISABLED=BAKED_NEXT_PUBLIC_SOURCEBOT_TELEMET
ARG NEXT_PUBLIC_SOURCEBOT_VERSION=BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION
ENV NEXT_PUBLIC_POSTHOG_PAPIK=BAKED_NEXT_PUBLIC_POSTHOG_PAPIK
ENV NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN

# @nocheckin: This was interfering with the the `matcher` regex in middleware.ts,
# causing regular expressions parsing errors when making a request. It's unclear
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -123,6 +123,12 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
# Always infer NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
export NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="$STRIPE_PUBLISHABLE_KEY"

# Always infer NEXT_PUBLIC_SENTRY_ENVIRONMENT
export NEXT_PUBLIC_SENTRY_ENVIRONMENT="$SENTRY_ENVIRONMENT"

# Always infer NEXT_PUBLIC_SENTRY_WEBAPP_DSN
export NEXT_PUBLIC_SENTRY_WEBAPP_DSN="$SENTRY_WEBAPP_DSN"

# Iterate over all .js files in .next & public, making substitutions for the `BAKED_` sentinal values
# with their actual desired runtime value.
find /app/packages/web/public /app/packages/web/.next -type f -name "*.js" |
Expand All@@ -131,6 +137,8 @@ echo "{\"version\": \"$SOURCEBOT_VERSION\", \"install_id\": \"$SOURCEBOT_INSTALL
sed -i "s|BAKED_NEXT_PUBLIC_SOURCEBOT_VERSION|${NEXT_PUBLIC_SOURCEBOT_VERSION}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_POSTHOG_PAPIK|${NEXT_PUBLIC_POSTHOG_PAPIK}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY|${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_ENVIRONMENT|${NEXT_PUBLIC_SENTRY_ENVIRONMENT}|g" "$file"
sed -i "s|BAKED_NEXT_PUBLIC_SENTRY_WEBAPP_DSN|${NEXT_PUBLIC_SENTRY_WEBAPP_DSN}|g" "$file"
done
}

Expand Down
4 changes: 3 additions & 1 deletion packages/backend/.gitignore
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
dist/
!.env
!.env
# Sentry Config File
.sentryclirc
10 changes: 7 additions & 3 deletions packages/backend/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,8 +7,9 @@
"scripts": {
"dev:watch": "tsc-watch --preserveWatchOutput --onSuccess \"yarn dev --cacheDir ../../.sourcebot\"",
"dev": "export PATH=\"$PWD/../../bin:$PATH\" && export CTAGS_COMMAND=ctags && node ./dist/index.js",
"build": "tsc",
"test": "vitest --config ./vitest.config.ts"
"build": "tsc && yarn sentry:sourcemaps",
"test": "vitest --config ./vitest.config.ts",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org sourcebot --project backend ./dist && sentry-cli sourcemaps upload --org sourcebot --project backend ./dist"
},
"devDependencies": {
"@types/argparse": "^2.0.16",
Expand All@@ -23,6 +24,9 @@
"dependencies": {
"@gitbeaker/rest": "^40.5.1",
"@octokit/rest": "^21.0.2",
"@sentry/cli": "^2.42.2",
"@sentry/node": "^9.3.0",
"@sentry/profiling-node": "^9.3.0",
"@sourcebot/crypto": "^0.1.0",
"@sourcebot/db": "^0.1.0",
"@sourcebot/error": "^0.1.0",
Expand All@@ -44,4 +48,4 @@
"strip-json-comments": "^5.0.1",
"winston": "^3.15.0"
}
}
}
7 changes: 6 additions & 1 deletion packages/backend/src/connectionManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ import { Redis } from 'ioredis';
import { RepoData, compileGithubConfig, compileGitlabConfig, compileGiteaConfig, compileGerritConfig } from "./repoCompileUtils.js";
import { BackendError, BackendException } from "@sourcebot/error";
import { captureEvent } from "./posthog.js";
import * as Sentry from "@sentry/node";

interface IConnectionManager {
scheduleConnectionSync: (connection: Connection) => Promise<void>;
Expand DownExpand Up@@ -94,9 +95,11 @@ export class ConnectionManager implements IConnectionManager {
});

if (!connection) {
throw new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
const e = new BackendException(BackendError.CONNECTION_SYNC_CONNECTION_NOT_FOUND, {
message: `Connection ${job.data.connectionId} not found`,
});
Sentry.captureException(e);
throw e;
}

// Reset the syncStatusMetadata to an empty object at the start of the sync job
Expand DownExpand Up@@ -146,6 +149,8 @@ export class ConnectionManager implements IConnectionManager {
})();
} catch (err) {
this.logger.error(`Failed to compile repo data for connection ${job.data.connectionId}: ${err}`);
Sentry.captureException(err);

if (err instanceof BackendException) {
throw err;
} else {
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/connectionUtils.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
import * as Sentry from "@sentry/node";

type ValidResult<T> = {
type: 'valid';
data: T[];
Expand DownExpand Up@@ -39,6 +41,7 @@ export function processPromiseResults<T>(
export function throwIfAnyFailed<T>(results: PromiseSettledResult<T>[]) {
const failedResult = results.find(result => result.status === 'rejected');
if (failedResult) {
Sentry.captureException(failedResult.reason);
throw failedResult.reason;
}
}
8 changes: 7 additions & 1 deletion packages/backend/src/environment.ts
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
import dotenv from 'dotenv';
import * as Sentry from "@sentry/node";

export const getEnv = (env: string | undefined, defaultValue?: string, required?: boolean) => {
if (required && !env && !defaultValue) {
throw new Error(`Missing required environment variable: ${env}`);
const e = new Error(`Missing required environment variable: ${env}`);
Sentry.captureException(e);
throw e;
}

return env ?? defaultValue;
Expand DownExpand Up@@ -37,3 +40,6 @@ export const FALLBACK_GITEA_TOKEN = getEnv(process.env.FALLBACK_GITEA_TOKEN);

export const INDEX_CONCURRENCY_MULTIPLE = getEnv(process.env.INDEX_CONCURRENCY_MULTIPLE);
export const REDIS_URL = getEnv(process.env.REDIS_URL, 'redis://localhost:6379')!;

export const SENTRY_BACKEND_DSN = getEnv(process.env.SENTRY_BACKEND_DSN);
export const SENTRY_ENVIRONMENT = getEnv(process.env.SENTRY_ENVIRONMENT, 'unknown')!;
11 changes: 9 additions & 2 deletions packages/backend/src/gerrit.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,7 @@ import micromatch from "micromatch";
import { measure, fetchWithRetry } from './utils.js';
import { BackendError } from '@sourcebot/error';
import { BackendException } from '@sourcebot/error';
import * as Sentry from "@sentry/node";

// https://gerrit-review.googlesource.com/Documentation/rest-api.html
interface GerritProjects {
Expand DownExpand Up@@ -40,6 +41,7 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
const fetchFn = () => fetchAllProjects(url);
return fetchWithRetry(fetchFn, `projects from ${url}`, logger);
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand All@@ -50,7 +52,9 @@ export const getGerritReposFromConfig = async (config: GerritConfig): Promise<Ge
});

if (!projects) {
throw new Error(`Failed to fetch projects from ${url}`);
const e = new Error(`Failed to fetch projects from ${url}`);
Sentry.captureException(e);
throw e;
}

// exclude "All-Projects" and "All-Users" projects
Expand DownExpand Up@@ -89,11 +93,14 @@ const fetchAllProjects = async (url: string): Promise<GerritProject[]> => {
response = await fetch(endpointWithParams);
if (!response.ok) {
console.log(`Failed to fetch projects from Gerrit at ${endpointWithParams} with status ${response.status}`);
throw new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
const e = new BackendException(BackendError.CONNECTION_SYNC_FAILED_TO_FETCH_GERRIT_PROJECTS, {
status: response.status,
});
Sentry.captureException(e);
throw e;
}
} catch (err) {
Sentry.captureException(err);
if (err instanceof BackendException) {
throw err;
}
Expand Down
12 changes: 11 additions & 1 deletion packages/backend/src/gitea.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import micromatch from 'micromatch';
import { PrismaClient } from '@sourcebot/db';
import { FALLBACK_GITEA_TOKEN } from './environment.js';
import { processPromiseResults, throwIfAnyFailed } from './connectionUtils.js';
import * as Sentry from "@sentry/node";

const logger = createLogger('Gitea');

export const getGiteaReposFromConfig = async (config: GiteaConnectionConfig, orgId: number, db: PrismaClient) => {
Expand DownExpand Up@@ -132,6 +134,8 @@ const getReposOwnedByUsers = async <T>(users: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -170,6 +174,8 @@ const getReposForOrgs = async <T>(orgs: string[], api: Api<T>) => {
data
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -206,6 +212,8 @@ const getRepos = async <T>(repos: string[], api: Api<T>) => {
data: [response.data]
};
} catch (e: any) {
Sentry.captureException(e);

if (e?.status === 404) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand DownExpand Up@@ -234,7 +242,9 @@ const paginate = async <T>(request: (page: number) => Promise<HttpResponse<T[],

const totalCountString = result.headers.get('x-total-count');
if (!totalCountString) {
throw new Error("Header 'x-total-count' not found");
const e = new Error("Header 'x-total-count' not found");
Sentry.captureException(e);
throw e;
}
const totalCount = parseInt(totalCountString);

Expand Down
18 changes: 16 additions & 2 deletions packages/backend/src/github.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,6 +7,8 @@ import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITHUB_TOKEN } from "./environment.js";
import { BackendException, BackendError } from "@sourcebot/error";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitHub");

export type OctokitRepository = {
Expand DownExpand Up@@ -53,15 +55,21 @@ export const getGitHubReposFromConfig = async (config: GithubConnectionConfig, o
try {
await octokit.rest.users.getAuthenticated();
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 401)) {
throw new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
const e = new BackendException(BackendError.CONNECTION_SYNC_INVALID_TOKEN, {
secretKey,
});
Sentry.captureException(e);
throw e;
}

throw new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
const e = new BackendException(BackendError.CONNECTION_SYNC_SYSTEM_ERROR, {
message: `Failed to authenticate with GitHub`,
});
Sentry.captureException(e);
throw e;
}
}

Expand DownExpand Up@@ -239,6 +247,8 @@ const getReposOwnedByUsers = async (users: string[], isAuthenticated: boolean, o
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`User ${user} not found or no access`);
return {
Expand DownExpand Up@@ -282,6 +292,8 @@ const getReposForOrgs = async (orgs: string[], octokit: Octokit, signal: AbortSi
data
};
} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Organization ${org} not found or no access`);
return {
Expand DownExpand Up@@ -327,6 +339,8 @@ const getRepos = async (repoList: string[], octokit: Octokit, signal: AbortSigna
};

} catch (error) {
Sentry.captureException(error);

if (isHttpError(error, 404)) {
logger.error(`Repository ${repo} not found or no access`);
return {
Expand Down
9 changes: 9 additions & 0 deletions packages/backend/src/gitlab.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,8 @@ import { getTokenFromConfig, measure, fetchWithRetry } from "./utils.js";
import { PrismaClient } from "@sourcebot/db";
import { FALLBACK_GITLAB_TOKEN } from "./environment.js";
import { processPromiseResults, throwIfAnyFailed } from "./connectionUtils.js";
import * as Sentry from "@sentry/node";

const logger = createLogger("GitLab");
export const GITLAB_CLOUD_HOSTNAME = "gitlab.com";

Expand DownExpand Up@@ -47,6 +49,7 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
logger.debug(`Found ${_projects.length} projects in ${durationMs}ms.`);
allRepos = allRepos.concat(_projects);
} catch (e) {
Sentry.captureException(e);
logger.error(`Failed to fetch all projects visible in ${config.url}.`, e);
throw e;
}
Expand All@@ -72,6 +75,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`Group ${group} not found or no access`);
Expand DownExpand Up@@ -106,6 +111,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;
if (status === 404) {
logger.error(`User ${user} not found or no access`);
Expand DownExpand Up@@ -138,6 +145,8 @@ export const getGitLabReposFromConfig = async (config: GitlabConnectionConfig, o
data: [data]
};
} catch (e: any) {
Sentry.captureException(e);

const status = e?.cause?.response?.status;

if (status === 404) {
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/index.ts
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
import "./instrument.js";

import * as Sentry from "@sentry/node";
import { ArgumentParser } from "argparse";
import { existsSync } from 'fs';
import { mkdir } from 'fs/promises';
Expand DownExpand Up@@ -48,6 +51,8 @@ main(prisma, context)
})
.catch(async (e) => {
console.error(e);
Sentry.captureException(e);

await prisma.$disconnect();
process.exit(1);
})
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/instrument.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
import * as Sentry from "@sentry/node";
import { SOURCEBOT_VERSION, SENTRY_BACKEND_DSN, SENTRY_ENVIRONMENT } from "./environment.js";

Sentry.init({
dsn: SENTRY_BACKEND_DSN,
release: SOURCEBOT_VERSION,
environment: SENTRY_ENVIRONMENT,
});
8 changes: 6 additions & 2 deletions packages/backend/src/repoManager.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,7 +10,7 @@ import { existsSync, readdirSync, promises } from 'fs';
import { indexGitRepository } from "./zoekt.js";
import os from 'os';
import { PromClient } from './promClient.js';

import * as Sentry from "@sentry/node";
interface IRepoManager {
blockingPollLoop: () => void;
dispose: () => void;
Expand DownExpand Up@@ -258,7 +258,9 @@ export class RepoManager implements IRepoManager {
});
if (!existingRepo) {
this.logger.error(`Repo ${repo.id} not found`);
throw new Error(`Repo ${repo.id} not found`);
const e = new Error(`Repo ${repo.id} not found`);
Sentry.captureException(e);
throw e;
}
const repoAlreadyInIndexingState = existingRepo.repoIndexingStatus === RepoIndexingStatus.INDEXING;

Expand DownExpand Up@@ -287,6 +289,8 @@ export class RepoManager implements IRepoManager {
stats = await this.syncGitRepository(repo, repoAlreadyInIndexingState);
break;
} catch (error) {
Sentry.captureException(error);

attempts++;
this.promClient.repoIndexingReattemptsTotal.inc();
if (attempts === maxAttempts) {
Expand Down
Loading