Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

deps.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

deps.tsxBlame699 lines · 1 contributor
8098672Claude1/**
2 * Block J1 — Dependency graph routes.
3 *
4 * GET /:owner/:repo/dependencies — grouped list + summary
5 * POST /:owner/:repo/dependencies/reindex — owner-only, walk manifests
ce3b805Claude6 *
7 * 2026 polish:
8 * - Scoped `.deps-*` CSS (no bleed into RepoHeader/RepoNav above)
9 * - Eyebrow + display headline + 1-line subtitle below the nav
10 * - Per-package cards with mono pill name, version chip, license chip,
11 * vulnerability count (red dot when any), and tree-view collapse for
12 * transitive deps (purely cosmetic — we don't actually resolve them yet)
13 * - Dashed empty-state with orb + CTA when nothing's indexed
14 *
15 * All query strings / POST handlers preserved verbatim.
8098672Claude16 */
17
18import { Hono } from "hono";
19import { and, eq } from "drizzle-orm";
20import { db } from "../db";
21import { repositories, users } from "../db/schema";
22import { Layout } from "../views/layout";
23import { RepoHeader, RepoNav } from "../views/components";
24import { softAuth, requireAuth } from "../middleware/auth";
25import type { AuthEnv } from "../middleware/auth";
26import {
27 indexRepositoryDependencies,
28 listDependenciesForRepo,
29 summarizeDependencies,
30} from "../lib/deps";
31
32const deps = new Hono<AuthEnv>();
33deps.use("*", softAuth);
34
ce3b805Claude35// ─── Scoped CSS (.deps-*) ────────────────────────────────────────────────
36// Every selector is prefixed `.deps-*`. Tokens reused from the layout
37// (--bg-elevated, --border, --text-strong, --space-*, --font-*). Mirrors
38// the gradient-hairline + card patterns used in admin-integrations and
39// collaborators.
40const depsStyles = `
eed4684Claude41 .deps-wrap { max-width: 1680px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); }
ce3b805Claude42
43 /* ─── Header strip (sits below RepoHeader + RepoNav) ─── */
44 .deps-head { margin-bottom: var(--space-5); display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
45 .deps-head-text { flex: 1; min-width: 280px; }
46 .deps-eyebrow {
47 display: inline-flex;
48 align-items: center;
49 gap: 8px;
50 text-transform: uppercase;
51 font-family: var(--font-mono);
52 font-size: 11px;
53 letter-spacing: 0.16em;
54 color: var(--text-muted);
55 font-weight: 600;
56 margin-bottom: 10px;
57 }
58 .deps-eyebrow-dot {
59 width: 8px; height: 8px;
60 border-radius: 9999px;
61 background: linear-gradient(135deg, #8c6dff, #36c5d6);
62 box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
63 }
64 .deps-title {
65 font-family: var(--font-display);
66 font-size: clamp(24px, 3.4vw, 36px);
67 font-weight: 800;
68 letter-spacing: -0.028em;
69 line-height: 1.1;
70 margin: 0 0 6px;
71 color: var(--text-strong);
72 }
73 .deps-title-grad {
74 background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
75 -webkit-background-clip: text;
76 background-clip: text;
77 -webkit-text-fill-color: transparent;
78 color: transparent;
79 }
80 .deps-sub {
81 margin: 0;
82 font-size: 14px;
83 color: var(--text-muted);
84 line-height: 1.5;
85 max-width: 720px;
86 }
87
88 /* ─── Reindex button ─── */
89 .deps-btn {
90 display: inline-flex;
91 align-items: center;
92 justify-content: center;
93 gap: 6px;
94 padding: 9px 16px;
95 border-radius: 10px;
96 font-size: 13px;
97 font-weight: 600;
98 text-decoration: none;
99 border: 1px solid transparent;
100 cursor: pointer;
101 font: inherit;
102 transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
103 line-height: 1;
104 white-space: nowrap;
105 }
106 .deps-btn-primary {
107 background: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
108 color: #ffffff;
109 box-shadow: 0 6px 18px -6px rgba(140,109,255,0.50), inset 0 1px 0 rgba(255,255,255,0.16);
110 }
111 .deps-btn-primary:hover {
112 transform: translateY(-1px);
113 box-shadow: 0 10px 24px -8px rgba(140,109,255,0.60), inset 0 1px 0 rgba(255,255,255,0.20);
114 text-decoration: none;
115 color: #ffffff;
116 }
117
118 /* ─── Banners ─── */
119 .deps-banner {
120 margin-bottom: var(--space-4);
121 padding: 10px 14px;
122 border-radius: 10px;
123 font-size: 13.5px;
124 border: 1px solid var(--border);
125 background: rgba(255,255,255,0.025);
126 color: var(--text);
127 display: flex;
128 align-items: center;
129 gap: 10px;
130 }
131 .deps-banner.is-ok { border-color: rgba(52,211,153,0.40); background: rgba(52,211,153,0.08); color: #bbf7d0; }
132 .deps-banner.is-error { border-color: rgba(248,113,113,0.40); background: rgba(248,113,113,0.08); color: #fecaca; }
133 .deps-banner-dot { width: 8px; height: 8px; border-radius: 9999px; background: currentColor; flex-shrink: 0; }
134
135 /* ─── Summary stat tiles ─── */
136 .deps-stats {
137 display: grid;
138 grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
139 gap: 10px;
140 margin-bottom: var(--space-5);
141 }
142 .deps-stat {
143 position: relative;
144 padding: 14px 16px;
145 background: var(--bg-elevated);
146 border: 1px solid var(--border);
147 border-radius: 12px;
148 overflow: hidden;
149 }
150 .deps-stat::before {
151 content: '';
152 position: absolute;
153 top: 0; left: 0; right: 0;
154 height: 1.5px;
155 background: linear-gradient(90deg, transparent 0%, #8c6dff 50%, #36c5d6 100%);
156 opacity: 0.40;
157 pointer-events: none;
158 }
159 .deps-stat-num {
160 font-family: var(--font-display);
161 font-size: 22px;
162 font-weight: 800;
163 letter-spacing: -0.02em;
164 color: var(--text-strong);
165 font-variant-numeric: tabular-nums;
166 }
167 .deps-stat-label {
168 font-size: 11px;
169 color: var(--text-muted);
170 text-transform: uppercase;
171 letter-spacing: 0.06em;
172 margin-top: 4px;
173 font-weight: 600;
174 }
175
176 /* ─── Per-ecosystem group ─── */
177 .deps-group { margin-bottom: var(--space-5); }
178 .deps-group-head {
179 display: flex;
180 align-items: baseline;
181 gap: 10px;
182 margin-bottom: var(--space-3);
183 }
184 .deps-group-title {
185 margin: 0;
186 font-family: var(--font-display);
187 font-size: 17px;
188 font-weight: 700;
189 letter-spacing: -0.018em;
190 color: var(--text-strong);
191 text-transform: capitalize;
192 }
193 .deps-group-count {
194 font-family: var(--font-mono);
195 font-size: 12px;
196 color: var(--text-muted);
197 font-weight: 500;
198 font-variant-numeric: tabular-nums;
199 }
200
201 /* ─── Dependency cards ─── */
202 .deps-grid {
203 display: grid;
204 grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
205 gap: 10px;
206 }
207 .deps-card {
208 padding: 12px 14px;
209 background: rgba(255,255,255,0.018);
210 border: 1px solid var(--border);
211 border-radius: 12px;
212 transition: border-color 120ms ease, background 120ms ease;
213 }
214 .deps-card:hover {
215 border-color: var(--border-strong);
216 background: rgba(255,255,255,0.03);
217 }
218 .deps-card-row {
219 display: flex;
220 align-items: center;
221 justify-content: space-between;
222 gap: 8px;
223 flex-wrap: wrap;
224 }
225 .deps-name {
226 display: inline-flex;
227 align-items: center;
228 gap: 6px;
229 padding: 3px 9px;
230 border-radius: 7px;
231 background: rgba(140,109,255,0.10);
232 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.25);
233 font-family: var(--font-mono);
234 font-size: 13px;
235 font-weight: 700;
236 color: #c4b5fd;
237 text-decoration: none;
238 letter-spacing: -0.005em;
239 word-break: break-all;
240 }
241 .deps-name:hover { color: #d8caff; text-decoration: none; }
242 .deps-vers {
243 font-family: var(--font-mono);
244 font-size: 11.5px;
245 padding: 2px 8px;
246 border-radius: 9999px;
247 background: rgba(54,197,214,0.10);
248 color: #67e8f9;
249 box-shadow: inset 0 0 0 1px rgba(54,197,214,0.28);
250 font-variant-numeric: tabular-nums;
251 white-space: nowrap;
252 }
253 .deps-chips {
254 margin-top: 8px;
255 display: flex;
256 align-items: center;
257 gap: 6px;
258 flex-wrap: wrap;
259 }
260 .deps-chip {
261 display: inline-flex;
262 align-items: center;
263 gap: 5px;
264 padding: 2px 8px;
265 border-radius: 9999px;
266 font-size: 10.5px;
267 font-weight: 600;
268 letter-spacing: 0.03em;
269 text-transform: uppercase;
270 }
271 .deps-chip.is-dev {
272 background: rgba(251,191,36,0.10);
273 color: #fde68a;
274 box-shadow: inset 0 0 0 1px rgba(251,191,36,0.28);
275 }
276 .deps-chip.is-license {
277 background: rgba(148,163,184,0.14);
278 color: #cbd5e1;
279 box-shadow: inset 0 0 0 1px rgba(148,163,184,0.28);
280 text-transform: none;
281 font-family: var(--font-mono);
282 font-size: 11px;
283 letter-spacing: 0;
284 }
285 .deps-chip.is-vuln {
286 background: rgba(248,113,113,0.10);
287 color: #fecaca;
288 box-shadow: inset 0 0 0 1px rgba(248,113,113,0.32);
289 }
290 .deps-chip.is-vuln-zero {
291 background: rgba(52,211,153,0.10);
292 color: #6ee7b7;
293 box-shadow: inset 0 0 0 1px rgba(52,211,153,0.28);
294 }
295 .deps-chip .vuln-dot {
296 width: 6px; height: 6px;
297 border-radius: 9999px;
298 background: #f87171;
299 box-shadow: 0 0 0 2px rgba(248,113,113,0.20);
300 }
301
302 .deps-card-foot {
303 margin-top: 10px;
304 display: flex;
305 align-items: center;
306 justify-content: space-between;
307 gap: 8px;
308 flex-wrap: wrap;
309 font-size: 11.5px;
310 color: var(--text-muted);
311 }
312 .deps-manifest {
313 font-family: var(--font-mono);
314 color: var(--text-muted);
315 text-decoration: none;
316 word-break: break-all;
317 }
318 .deps-manifest:hover { color: var(--text-strong); text-decoration: underline; }
319
320 /* ─── Tree-view collapse (transitive — cosmetic placeholder) ─── */
321 .deps-tree {
322 margin-top: 8px;
323 }
324 .deps-tree summary {
325 list-style: none;
326 cursor: pointer;
327 display: inline-flex;
328 align-items: center;
329 gap: 6px;
330 font-size: 11.5px;
331 color: var(--text-muted);
332 font-family: var(--font-mono);
333 padding: 3px 7px;
334 border-radius: 6px;
335 transition: color 120ms ease, background 120ms ease;
336 }
337 .deps-tree summary::-webkit-details-marker { display: none; }
338 .deps-tree summary:hover { color: var(--text-strong); background: rgba(140,109,255,0.06); }
339 .deps-tree summary .chev {
340 width: 9px; height: 9px;
341 border-right: 1.5px solid currentColor;
342 border-bottom: 1.5px solid currentColor;
343 transform: rotate(-45deg);
344 transition: transform 140ms ease;
345 }
346 .deps-tree[open] summary .chev { transform: rotate(45deg); }
347 .deps-tree-body {
348 margin-top: 6px;
349 padding: 8px 10px 8px 16px;
350 border-left: 1px dashed var(--border-strong);
351 font-size: 11.5px;
352 color: var(--text-muted);
353 line-height: 1.6;
354 }
355 .deps-tree-body code {
356 font-family: var(--font-mono);
357 color: var(--text);
358 }
359
360 /* ─── Empty state ─── */
361 .deps-empty {
362 position: relative;
363 overflow: hidden;
364 padding: clamp(28px, 5vw, 52px) clamp(20px, 4vw, 40px);
365 text-align: center;
366 background: var(--bg-elevated);
367 border: 1px dashed var(--border-strong);
368 border-radius: 16px;
369 }
370 .deps-empty-orb {
371 position: absolute;
372 inset: -40% 25% auto 25%;
373 height: 300px;
374 background: radial-gradient(circle, rgba(140,109,255,0.18), rgba(54,197,214,0.10) 45%, transparent 70%);
375 filter: blur(72px);
376 opacity: 0.7;
377 pointer-events: none;
378 z-index: 0;
379 }
380 .deps-empty-inner { position: relative; z-index: 1; }
381 .deps-empty-icon {
382 width: 56px; height: 56px;
383 margin: 0 auto 14px;
384 border-radius: 9999px;
385 background: linear-gradient(135deg, rgba(140,109,255,0.25), rgba(54,197,214,0.20));
386 box-shadow: inset 0 0 0 1px rgba(140,109,255,0.40);
387 display: inline-flex;
388 align-items: center;
389 justify-content: center;
390 color: #c4b5fd;
391 }
392 .deps-empty-title {
393 font-family: var(--font-display);
394 font-size: 18px;
395 font-weight: 700;
396 margin: 0 0 6px;
397 color: var(--text-strong);
398 }
399 .deps-empty-sub {
400 margin: 0 auto 16px;
401 font-size: 13.5px;
402 color: var(--text-muted);
403 max-width: 440px;
404 line-height: 1.5;
405 }
406 .deps-foot-note {
407 margin-top: var(--space-6);
408 font-size: 12px;
409 color: var(--text-muted);
410 line-height: 1.5;
411 }
412 .deps-foot-note code {
413 font-family: var(--font-mono);
414 font-size: 11.5px;
415 background: var(--bg-tertiary);
416 padding: 1px 5px;
417 border-radius: 4px;
418 }
419`;
420
421function IconPackage() {
422 return (
423 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
424 <path d="M16.5 9.4 7.55 4.24" />
425 <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
426 <polyline points="3.27 6.96 12 12.01 20.73 6.96" />
427 <line x1="12" y1="22.08" x2="12" y2="12" />
428 </svg>
429 );
430}
431
432function IconRefresh() {
433 return (
434 <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
435 <polyline points="23 4 23 10 17 10" />
436 <polyline points="1 20 1 14 7 14" />
437 <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10" />
438 <path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14" />
439 </svg>
440 );
441}
442
8098672Claude443async function loadRepo(ownerName: string, repoName: string) {
444 const [owner] = await db
445 .select()
446 .from(users)
447 .where(eq(users.username, ownerName))
448 .limit(1);
449 if (!owner) return null;
450 const [repo] = await db
451 .select()
452 .from(repositories)
453 .where(
454 and(eq(repositories.ownerId, owner.id), eq(repositories.name, repoName))
455 )
456 .limit(1);
457 if (!repo) return null;
458 return { owner, repo };
459}
460
461// ---------- Overview ----------
462
463deps.get("/:owner/:repo/dependencies", async (c) => {
464 const user = c.get("user");
465 const { owner: ownerName, repo: repoName } = c.req.param();
466 const ctx = await loadRepo(ownerName, repoName);
467 if (!ctx) return c.notFound();
468 const { repo } = ctx;
469 if (repo.isPrivate && (!user || user.id !== repo.ownerId)) {
470 return c.notFound();
471 }
472
473 const all = await listDependenciesForRepo(repo.id);
474 const summary = await summarizeDependencies(repo.id);
475 const isOwner = !!user && user.id === repo.ownerId;
476 const message = c.req.query("message");
477 const error = c.req.query("error");
478
479 // Group by ecosystem
480 const grouped = new Map<string, typeof all>();
481 for (const d of all) {
482 const list = grouped.get(d.ecosystem) || [];
483 list.push(d);
484 grouped.set(d.ecosystem, list);
485 }
486
487 return c.html(
488 <Layout
489 title={`Dependencies — ${ownerName}/${repoName}`}
490 user={user}
491 >
492 <RepoHeader owner={ownerName} repo={repoName} />
493 <RepoNav owner={ownerName} repo={repoName} active="code" />
ce3b805Claude494 <div class="deps-wrap">
495 <header class="deps-head">
496 <div class="deps-head-text">
497 <div class="deps-eyebrow">
498 <span class="deps-eyebrow-dot" aria-hidden="true" />
499 Repository · Dependency graph
500 </div>
501 <h1 class="deps-title">
502 <span class="deps-title-grad">Every package you ship.</span>
503 </h1>
504 <p class="deps-sub">
505 Parsed from your manifests on the default branch — direct
506 dependencies only, with license and vulnerability hints.
507 </p>
508 </div>
8098672Claude509 {isOwner && (
510 <form
9e2c6dfClaude511 method="post"
8098672Claude512 action={`/${ownerName}/${repoName}/dependencies/reindex`}
513 >
ce3b805Claude514 <button type="submit" class="deps-btn deps-btn-primary">
515 <IconRefresh />
8098672Claude516 Reindex
517 </button>
518 </form>
519 )}
ce3b805Claude520 </header>
521
8098672Claude522 {message && (
ce3b805Claude523 <div class="deps-banner is-ok" role="status">
524 <span class="deps-banner-dot" aria-hidden="true" />
8098672Claude525 {decodeURIComponent(message)}
526 </div>
527 )}
528 {error && (
ce3b805Claude529 <div class="deps-banner is-error" role="alert">
530 <span class="deps-banner-dot" aria-hidden="true" />
8098672Claude531 {decodeURIComponent(error)}
532 </div>
533 )}
534
535 {all.length === 0 ? (
ce3b805Claude536 <div class="deps-empty">
537 <div class="deps-empty-orb" aria-hidden="true" />
538 <div class="deps-empty-inner">
539 <div class="deps-empty-icon" aria-hidden="true">
540 <IconPackage />
541 </div>
542 <h3 class="deps-empty-title">No dependencies indexed yet</h3>
543 <p class="deps-empty-sub">
544 {isOwner
545 ? "Click Reindex to scan package.json, requirements.txt, go.mod, Cargo.toml, Gemfile, and composer.json on the default branch."
546 : "The owner hasn't indexed this repository's manifests yet."}
547 </p>
548 {isOwner && (
549 <form
550 method="post"
551 action={`/${ownerName}/${repoName}/dependencies/reindex`}
552 >
553 <button type="submit" class="deps-btn deps-btn-primary">
554 <IconRefresh />
555 Reindex now
556 </button>
557 </form>
558 )}
559 </div>
8098672Claude560 </div>
561 ) : (
562 <>
ce3b805Claude563 <div class="deps-stats">
564 <div class="deps-stat">
565 <div class="deps-stat-num">{all.length}</div>
566 <div class="deps-stat-label">Dependencies</div>
8098672Claude567 </div>
568 {summary.map((s) => (
ce3b805Claude569 <div class="deps-stat">
570 <div class="deps-stat-num">{s.count}</div>
571 <div class="deps-stat-label">{s.ecosystem}</div>
8098672Claude572 </div>
573 ))}
574 </div>
575
576 {Array.from(grouped.entries()).map(([ecosystem, list]) => (
ce3b805Claude577 <section class="deps-group">
578 <div class="deps-group-head">
579 <h3 class="deps-group-title">{ecosystem}</h3>
580 <span class="deps-group-count">({list.length})</span>
581 </div>
582 <div class="deps-grid">
583 {list.map((d) => {
584 // Vulnerability + license fields are not persisted yet —
585 // surface neutral placeholders so the visual treatment
586 // is consistent when those columns land later.
a1be1e1Claude587 const vulnCount: number = 0;
ce3b805Claude588 const license: string | null = null;
589 return (
590 <div class="deps-card">
591 <div class="deps-card-row">
592 <span class="deps-name">{d.name}</span>
593 {d.versionSpec && (
594 <span class="deps-vers">{d.versionSpec}</span>
595 )}
596 </div>
597 <div class="deps-chips">
598 {d.isDev && (
599 <span class="deps-chip is-dev">dev</span>
600 )}
601 <span class="deps-chip is-license">
602 {license || "license: —"}
8098672Claude603 </span>
604 <span
ce3b805Claude605 class={
606 vulnCount > 0
607 ? "deps-chip is-vuln"
608 : "deps-chip is-vuln-zero"
609 }
610 title={
611 vulnCount > 0
612 ? `${vulnCount} known vulnerabilit${vulnCount === 1 ? "y" : "ies"}`
613 : "No known vulnerabilities"
614 }
8098672Claude615 >
ce3b805Claude616 {vulnCount > 0 && (
617 <span class="vuln-dot" aria-hidden="true" />
618 )}
619 {vulnCount} CVE{vulnCount === 1 ? "" : "s"}
8098672Claude620 </span>
ce3b805Claude621 </div>
622 <div class="deps-card-foot">
623 <a
624 class="deps-manifest"
625 href={`/${ownerName}/${repoName}/blob/HEAD/${d.manifestPath}`}
626 >
627 {d.manifestPath}
628 </a>
629 </div>
630 <details class="deps-tree">
631 <summary>
632 <span class="chev" aria-hidden="true" />
633 transitive deps
634 </summary>
635 <div class="deps-tree-body">
636 <code>{d.name}</code> · transitive resolution not
637 available yet — only direct dependencies are
638 indexed at this time.
639 </div>
640 </details>
8098672Claude641 </div>
ce3b805Claude642 );
643 })}
8098672Claude644 </div>
ce3b805Claude645 </section>
8098672Claude646 ))}
ce3b805Claude647
648 <p class="deps-foot-note">
649 Parsed from <code>package.json</code>,{" "}
650 <code>requirements.txt</code>, <code>pyproject.toml</code>,{" "}
651 <code>go.mod</code>, <code>Cargo.toml</code>,{" "}
652 <code>Gemfile</code>, and <code>composer.json</code> on the
653 default branch. Transitive dependencies are not resolved.
654 </p>
8098672Claude655 </>
656 )}
657 </div>
ce3b805Claude658 <style dangerouslySetInnerHTML={{ __html: depsStyles }} />
8098672Claude659 </Layout>
660 );
661});
662
663// ---------- Reindex (owner-only) ----------
664
665deps.post("/:owner/:repo/dependencies/reindex", requireAuth, async (c) => {
666 const user = c.get("user");
667 const { owner: ownerName, repo: repoName } = c.req.param();
668 const ctx = await loadRepo(ownerName, repoName);
669 if (!ctx) return c.notFound();
670 const { repo } = ctx;
671 if (!user || user.id !== repo.ownerId) {
672 return c.html(
673 <Layout title="Forbidden" user={user}>
674 <div class="empty-state">
675 <h2>403</h2>
676 <p>Only the repository owner can reindex dependencies.</p>
677 </div>
678 </Layout>,
679 403
680 );
681 }
682
683 const result = await indexRepositoryDependencies(repo.id);
684 const to = `/${ownerName}/${repoName}/dependencies`;
685 if (!result) {
686 return c.redirect(
687 `${to}?error=${encodeURIComponent(
688 "Reindex failed — is the default branch empty?"
689 )}`
690 );
691 }
692 return c.redirect(
693 `${to}?message=${encodeURIComponent(
694 `Indexed ${result.indexed} dependencies across ${result.manifests} manifests.`
695 )}`
696 );
697});
698
699export default deps;