diff --git a/.release-please-manifest.develop.json b/.release-please-manifest.develop.json index 81d4017..4cdec26 100644 --- a/.release-please-manifest.develop.json +++ b/.release-please-manifest.develop.json @@ -1,5 +1,5 @@ { - ".": "0.1.0-alpha.8", - "apps/web": "0.1.0-alpha.8", + ".": "0.1.0-alpha.9", + "apps/web": "0.1.0-alpha.9", "apps/worker": "0.1.0-alpha.3" } diff --git a/CHANGELOG-DEVELOP.md b/CHANGELOG-DEVELOP.md index 85da544..6dc4097 100644 --- a/CHANGELOG-DEVELOP.md +++ b/CHANGELOG-DEVELOP.md @@ -1,5 +1,17 @@ # Changelog +## [0.1.0-alpha.9](https://github.com/devakone/vibe-coding-profiler/compare/vibe-coding-profiler-v0.1.0-alpha.8...vibe-coding-profiler-v0.1.0-alpha.9) (2026-01-30) + + +### Features + +* add script to retry queued analysis jobs by re-sending Inngest events ([aae9300](https://github.com/devakone/vibe-coding-profiler/commit/aae930065a767bfb2f22d12989dc7143b4d2e94a)) + + +### Bug Fixes + +* disable Get Vibe button while analysis job is running ([4a07531](https://github.com/devakone/vibe-coding-profiler/commit/4a0753184ce97d9410591ac991522a17301a41be)) + ## [0.1.0-alpha.8](https://github.com/devakone/vibe-coding-profiler/compare/vibe-coding-profiler-v0.1.0-alpha.7...vibe-coding-profiler-v0.1.0-alpha.8) (2026-01-30) diff --git a/apps/web/CHANGELOG-DEVELOP.md b/apps/web/CHANGELOG-DEVELOP.md index 89fae73..3f2e060 100644 --- a/apps/web/CHANGELOG-DEVELOP.md +++ b/apps/web/CHANGELOG-DEVELOP.md @@ -1,5 +1,12 @@ # Changelog +## [0.1.0-alpha.9](https://github.com/devakone/vibe-coding-profiler/compare/web-v0.1.0-alpha.8...web-v0.1.0-alpha.9) (2026-01-30) + + +### Bug Fixes + +* disable Get Vibe button while analysis job is running ([4a07531](https://github.com/devakone/vibe-coding-profiler/commit/4a0753184ce97d9410591ac991522a17301a41be)) + ## [0.1.0-alpha.8](https://github.com/devakone/vibe-coding-profiler/compare/web-v0.1.0-alpha.7...web-v0.1.0-alpha.8) (2026-01-30) diff --git a/apps/web/src/app/vibes/VibesClient.tsx b/apps/web/src/app/vibes/VibesClient.tsx index 39ae9ee..9801328 100644 --- a/apps/web/src/app/vibes/VibesClient.tsx +++ b/apps/web/src/app/vibes/VibesClient.tsx @@ -7,6 +7,7 @@ import { ChevronRight, ChevronDown } from "lucide-react"; import { wrappedTheme } from "@/lib/theme"; import { toast } from "@/components/ui/use-toast"; import { ToastAction } from "@/components/ui/toast"; +import { useJobs } from "@/contexts/JobsContext"; type VibeVersion = { jobId: string; @@ -40,9 +41,17 @@ function formatDate(dateStr: string | null): string { export default function VibesClient({ repos }: VibesClientProps) { const router = useRouter(); + const { jobs, refreshJobs } = useJobs(); const [expandedRepos, setExpandedRepos] = useState>(new Set()); const [loadingRepoId, setLoadingRepoId] = useState(null); + const activeJobRepoIds = new Set( + jobs + .filter((j) => j.status === "pending" || j.status === "running" || j.status === "queued") + .map((j) => j.repoId) + .filter((id): id is string => id !== null) + ); + const toggleExpanded = (repoId: string) => { setExpandedRepos((prev) => { const next = new Set(prev); @@ -79,6 +88,7 @@ export default function VibesClient({ repos }: VibesClientProps) { ), }); + await refreshJobs(); router.refresh(); } catch (e) { toast({ @@ -114,6 +124,8 @@ export default function VibesClient({ repos }: VibesClientProps) { {repos.map((repo) => { const isExpanded = expandedRepos.has(repo.repoId); const isLoading = loadingRepoId === repo.repoId; + const hasActiveJob = activeJobRepoIds.has(repo.repoId); + const isBusy = isLoading || hasActiveJob; return (
@@ -169,20 +181,20 @@ export default function VibesClient({ repos }: VibesClientProps) { ) : ( )}
diff --git a/scripts/retry-queued-jobs.mjs b/scripts/retry-queued-jobs.mjs new file mode 100644 index 0000000..62f451c --- /dev/null +++ b/scripts/retry-queued-jobs.mjs @@ -0,0 +1,128 @@ +/** + * Retry all queued analysis jobs by re-sending Inngest events. + * + * Usage: + * INNGEST_EVENT_KEY= node scripts/retry-queued-jobs.mjs + * + * The Inngest event key can be found in the Inngest dashboard or Vercel env vars. + * Supabase credentials are loaded from apps/web/.env.local / .env automatically. + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +// ── env loading (reused from supabase.mjs) ────────────────────────────────── + +function loadDotEnvFile(filePath) { + if (!fs.existsSync(filePath)) return; + const contents = fs.readFileSync(filePath, "utf8").replace(/^\uFEFF/, ""); + for (const line of contents.split(/\r?\n/)) { + const trimmed = line.trim().replace(/^export\s+/, ""); + if (!trimmed || trimmed.startsWith("#")) continue; + const eq = trimmed.indexOf("="); + if (eq === -1) continue; + const key = trimmed.slice(0, eq).trim(); + let value = trimmed.slice(eq + 1).trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + value = value.slice(1, -1); + } + if (key && (process.env[key] === undefined || process.env[key] === "")) { + process.env[key] = value; + } + } +} + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +loadDotEnvFile(path.join(repoRoot, "apps/web/.env.local")); +loadDotEnvFile(path.join(repoRoot, "apps/web/.env")); + +// ── config ────────────────────────────────────────────────────────────────── + +const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL; +const SERVICE_ROLE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY; +const INNGEST_EVENT_KEY = process.env.INNGEST_EVENT_KEY; + +if (!SUPABASE_URL || !SERVICE_ROLE_KEY) { + console.error("Missing NEXT_PUBLIC_SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY"); + process.exit(1); +} + +if (!INNGEST_EVENT_KEY) { + console.error( + "Missing INNGEST_EVENT_KEY. Get it from the Inngest dashboard or Vercel env vars.\n" + + "Usage: INNGEST_EVENT_KEY= node scripts/retry-queued-jobs.mjs" + ); + process.exit(1); +} + +// ── main ──────────────────────────────────────────────────────────────────── + +async function main() { + // 1. Fetch all queued jobs + const url = `${SUPABASE_URL}/rest/v1/analysis_jobs?select=id,user_id,repo_id,created_at&status=eq.queued&order=created_at.asc`; + const res = await fetch(url, { + headers: { + apikey: SERVICE_ROLE_KEY, + Authorization: `Bearer ${SERVICE_ROLE_KEY}`, + }, + }); + + if (!res.ok) { + console.error("Failed to fetch queued jobs:", res.status, await res.text()); + process.exit(1); + } + + const jobs = await res.json(); + + if (jobs.length === 0) { + console.log("No queued jobs found."); + return; + } + + console.log(`Found ${jobs.length} queued job(s). Re-sending Inngest events...\n`); + + // 2. Send an Inngest event for each job + let success = 0; + let failed = 0; + + for (const job of jobs) { + const event = { + name: "repo/analyze.requested", + data: { + jobId: job.id, + userId: job.user_id, + repoId: job.repo_id, + }, + }; + + try { + const inngestRes = await fetch(`https://inn.gs/e/${INNGEST_EVENT_KEY}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(event), + }); + + if (inngestRes.ok) { + console.log(` ✓ ${job.id} (created ${job.created_at})`); + success++; + } else { + const body = await inngestRes.text(); + console.error(` ✗ ${job.id}: ${inngestRes.status} ${body}`); + failed++; + } + } catch (err) { + console.error(` ✗ ${job.id}: ${err.message}`); + failed++; + } + } + + console.log(`\nDone. ${success} sent, ${failed} failed.`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +});