HuggingClaw-MissionControl / src /lib /github-sync-poller.ts
nyk
feat(refactor): ready for manual QA after main sync (#274)
b6ecafa unverified
/**
* Background poller for GitHub ↔ MC task sync.
* Lazy singleton — call startSyncPoller() to begin.
*/
import { getDatabase } from '@/lib/db'
import { logger } from '@/lib/logger'
import { pullFromGitHub } from '@/lib/github-sync-engine'
const INTERVAL_MS = parseInt(process.env.GITHUB_SYNC_INTERVAL_MS || '60000', 10)
let intervalHandle: ReturnType<typeof setInterval> | null = null
let lastRun: number | undefined
export function startSyncPoller(): void {
if (intervalHandle) return
logger.info({ intervalMs: INTERVAL_MS }, 'Starting GitHub sync poller')
intervalHandle = setInterval(async () => {
await runSyncTick()
}, INTERVAL_MS)
// Run immediately on start
runSyncTick().catch(() => {})
}
export function stopSyncPoller(): void {
if (intervalHandle) {
clearInterval(intervalHandle)
intervalHandle = null
logger.info('GitHub sync poller stopped')
}
}
export function getSyncPollerStatus(): { running: boolean; interval: number; lastRun?: number } {
return {
running: intervalHandle !== null,
interval: INTERVAL_MS,
lastRun,
}
}
async function runSyncTick(): Promise<void> {
try {
const db = getDatabase()
const projects = db.prepare(`
SELECT id, github_repo, github_sync_enabled, github_default_branch, workspace_id
FROM projects
WHERE github_sync_enabled = 1 AND github_repo IS NOT NULL AND status = 'active'
`).all() as Array<{
id: number
github_repo: string
github_sync_enabled: number
github_default_branch: string | null
workspace_id: number
}>
for (const project of projects) {
try {
await pullFromGitHub(project, project.workspace_id)
} catch (err) {
logger.error({ err, projectId: project.id, repo: project.github_repo }, 'Sync poller: project sync failed')
}
}
lastRun = Math.floor(Date.now() / 1000)
} catch (err) {
logger.error({ err }, 'Sync poller tick failed')
}
}