import "./instrument.js"; import * as Sentry from "@sentry/node"; import { PrismaClient } from "@sourcebot/db"; import { createLogger, env, getConfigSettings, getDBConnectionString, hasEntitlement } from "@sourcebot/shared"; import 'express-async-errors'; import { existsSync } from 'fs'; import { mkdir } from 'fs/promises'; import { Redis } from 'ioredis'; import { Api } from "./api.js"; import { ConfigManager } from "./configManager.js"; import { ConnectionManager } from './connectionManager.js'; import { INDEX_CACHE_DIR, REPOS_CACHE_DIR, SHUTDOWN_SIGNALS } from './constants.js'; import { AccountPermissionSyncer } from "./ee/accountPermissionSyncer.js"; import { GithubAppManager } from "./ee/githubAppManager.js"; import { RepoPermissionSyncer } from './ee/repoPermissionSyncer.js'; import { shutdownPosthog } from "./posthog.js"; import { PromClient } from './promClient.js'; import { RepoIndexManager } from "./repoIndexManager.js"; const logger = createLogger('backend-entrypoint'); const reposPath = REPOS_CACHE_DIR; const indexPath = INDEX_CACHE_DIR; if (!existsSync(reposPath)) { await mkdir(reposPath, { recursive: true }); } if (!existsSync(indexPath)) { await mkdir(indexPath, { recursive: true }); } const prisma = new PrismaClient({ datasources: { db: { url: getDBConnectionString(), }, }, }); const redis = new Redis(env.REDIS_URL, { maxRetriesPerRequest: null }); try { await redis.ping(); logger.info('Connected to redis'); } catch (err: unknown) { logger.error('Failed to connect to redis. Error:', err); process.exit(1); } const promClient = new PromClient(); const settings = await getConfigSettings(env.CONFIG_PATH); if (hasEntitlement('github-app')) { await GithubAppManager.getInstance().init(prisma); } const connectionManager = new ConnectionManager(prisma, settings, redis, promClient); const repoPermissionSyncer = new RepoPermissionSyncer(prisma, settings, redis); const accountPermissionSyncer = new AccountPermissionSyncer(prisma, settings, redis); const repoIndexManager = new RepoIndexManager(prisma, settings, redis, promClient); const configManager = new ConfigManager(prisma, connectionManager, env.CONFIG_PATH); connectionManager.startScheduler(); repoIndexManager.startScheduler(); if (env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && !hasEntitlement('permission-syncing')) { logger.error('Permission syncing is not supported in current plan. Please contact team@sourcebot.dev for assistance.'); process.exit(1); } else if (env.EXPERIMENT_EE_PERMISSION_SYNC_ENABLED === 'true' && hasEntitlement('permission-syncing')) { repoPermissionSyncer.startScheduler(); accountPermissionSyncer.startScheduler(); } const api = new Api( promClient, prisma, connectionManager, repoIndexManager, ); logger.info('Worker started.'); const listenToShutdownSignals = () => { const signals = SHUTDOWN_SIGNALS; let receivedSignal = false; const cleanup = async (signal: string) => { try { if (receivedSignal) { return; } receivedSignal = true; logger.info(`Received ${signal}, cleaning up...`); await repoIndexManager.dispose() await connectionManager.dispose() await repoPermissionSyncer.dispose() await accountPermissionSyncer.dispose() await configManager.dispose() await prisma.$disconnect(); await redis.quit(); await api.dispose(); await shutdownPosthog(); logger.info('All workers shut down gracefully'); signals.forEach(sig => process.removeListener(sig, cleanup)); return 0; } catch (error) { Sentry.captureException(error); logger.error('Error shutting down worker:', error); return 1; } } signals.forEach(signal => { process.on(signal, (err) => { cleanup(err).then(code => { process.exit(code); }); }); }); // Register handlers for uncaught exceptions and unhandled rejections process.on('uncaughtException', (err) => { logger.error(`Uncaught exception: ${err.message}`); cleanup('uncaughtException').then(() => { process.exit(1); }); }); process.on('unhandledRejection', (reason, promise) => { logger.error(`Unhandled rejection at: ${promise}, reason: ${reason}`); cleanup('unhandledRejection').then(() => { process.exit(1); }); }); } listenToShutdownSignals();