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
4 changes: 2 additions & 2 deletions .release-please-manifest.develop.json
Original file line number Diff line number Diff line change
@@ -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"
}
12 changes: 12 additions & 0 deletions CHANGELOG-DEVELOP.md
Original file line number Diff line number Diff line change
@@ -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)


Expand Down
7 changes: 7 additions & 0 deletions apps/web/CHANGELOG-DEVELOP.md
Original file line number Diff line number Diff line change
@@ -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)


Expand Down
20 changes: 16 additions & 4 deletions apps/web/src/app/vibes/VibesClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Set<string>>(new Set());
const [loadingRepoId, setLoadingRepoId] = useState<string | null>(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);
Expand Down Expand Up @@ -79,6 +88,7 @@ export default function VibesClient({ repos }: VibesClientProps) {
</ToastAction>
),
});
await refreshJobs();
router.refresh();
} catch (e) {
toast({
Expand Down Expand Up @@ -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 (
<div key={repo.repoId}>
Expand Down Expand Up @@ -169,20 +181,20 @@ export default function VibesClient({ repos }: VibesClientProps) {
<button
type="button"
onClick={() => startAnalysis(repo.repoId, repo.repoName)}
disabled={isLoading}
disabled={isBusy}
className="rounded-full border border-zinc-300/80 bg-white/70 px-3 py-1 text-sm font-semibold text-zinc-950 shadow-sm backdrop-blur transition hover:bg-white disabled:opacity-60"
>
{isLoading ? "Starting..." : "Re-run"}
{hasActiveJob ? "Analyzing…" : isLoading ? "Starting..." : "Re-run"}
</button>
</>
) : (
<button
type="button"
onClick={() => startAnalysis(repo.repoId, repo.repoName)}
disabled={isLoading}
disabled={isBusy}
className={`${wrappedTheme.primaryButtonSm} disabled:opacity-60`}
>
{isLoading ? "Starting..." : "Get Vibe"}
{hasActiveJob ? "Analyzing…" : isLoading ? "Starting..." : "Get Vibe"}
</button>
)}
</div>
Expand Down
128 changes: 128 additions & 0 deletions scripts/retry-queued-jobs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Retry all queued analysis jobs by re-sending Inngest events.
*
* Usage:
* INNGEST_EVENT_KEY=<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=<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);
});