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.tsxBlame233 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
6 */
7
8import { Hono } from "hono";
9import { and, eq } from "drizzle-orm";
10import { db } from "../db";
11import { repositories, users } from "../db/schema";
12import { Layout } from "../views/layout";
13import { RepoHeader, RepoNav } from "../views/components";
14import { softAuth, requireAuth } from "../middleware/auth";
15import type { AuthEnv } from "../middleware/auth";
16import {
17 indexRepositoryDependencies,
18 listDependenciesForRepo,
19 summarizeDependencies,
20} from "../lib/deps";
21
22const deps = new Hono<AuthEnv>();
23deps.use("*", softAuth);
24
25async function loadRepo(ownerName: string, repoName: string) {
26 const [owner] = await db
27 .select()
28 .from(users)
29 .where(eq(users.username, ownerName))
30 .limit(1);
31 if (!owner) return null;
32 const [repo] = await db
33 .select()
34 .from(repositories)
35 .where(
36 and(eq(repositories.ownerId, owner.id), eq(repositories.name, repoName))
37 )
38 .limit(1);
39 if (!repo) return null;
40 return { owner, repo };
41}
42
43// ---------- Overview ----------
44
45deps.get("/:owner/:repo/dependencies", async (c) => {
46 const user = c.get("user");
47 const { owner: ownerName, repo: repoName } = c.req.param();
48 const ctx = await loadRepo(ownerName, repoName);
49 if (!ctx) return c.notFound();
50 const { repo } = ctx;
51 if (repo.isPrivate && (!user || user.id !== repo.ownerId)) {
52 return c.notFound();
53 }
54
55 const all = await listDependenciesForRepo(repo.id);
56 const summary = await summarizeDependencies(repo.id);
57 const isOwner = !!user && user.id === repo.ownerId;
58 const message = c.req.query("message");
59 const error = c.req.query("error");
60
61 // Group by ecosystem
62 const grouped = new Map<string, typeof all>();
63 for (const d of all) {
64 const list = grouped.get(d.ecosystem) || [];
65 list.push(d);
66 grouped.set(d.ecosystem, list);
67 }
68
69 return c.html(
70 <Layout
71 title={`Dependencies — ${ownerName}/${repoName}`}
72 user={user}
73 >
74 <RepoHeader owner={ownerName} repo={repoName} />
75 <RepoNav owner={ownerName} repo={repoName} active="code" />
76 <div class="settings-container">
77 <div style="display:flex;justify-content:space-between;align-items:center">
78 <h2 style="margin:0">Dependencies</h2>
79 {isOwner && (
80 <form
9e2c6dfClaude81 method="post"
8098672Claude82 action={`/${ownerName}/${repoName}/dependencies/reindex`}
83 >
84 <button type="submit" class="btn btn-primary btn-sm">
85 Reindex
86 </button>
87 </form>
88 )}
89 </div>
90 {message && (
91 <div class="auth-success" style="margin-top:12px">
92 {decodeURIComponent(message)}
93 </div>
94 )}
95 {error && (
96 <div class="auth-error" style="margin-top:12px">
97 {decodeURIComponent(error)}
98 </div>
99 )}
100 <p style="color:var(--text-muted);margin-top:8px">
101 Parsed from <code>package.json</code>, <code>requirements.txt</code>,{" "}
102 <code>pyproject.toml</code>, <code>go.mod</code>,{" "}
103 <code>Cargo.toml</code>, <code>Gemfile</code>, and{" "}
104 <code>composer.json</code> on the default branch. Transitive
105 dependencies are not resolved.
106 </p>
107
108 {all.length === 0 ? (
109 <div class="panel-empty" style="padding:24px">
110 No dependencies indexed yet.
111 {isOwner && " Click Reindex to scan the repository."}
112 </div>
113 ) : (
114 <>
115 <div
116 style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px;margin:16px 0"
117 >
118 <div class="panel" style="padding:12px;text-align:center">
119 <div style="font-size:20px;font-weight:700">
120 {all.length}
121 </div>
122 <div
123 style="font-size:11px;color:var(--text-muted);text-transform:uppercase"
124 >
125 Dependencies
126 </div>
127 </div>
128 {summary.map((s) => (
129 <div class="panel" style="padding:12px;text-align:center">
130 <div style="font-size:20px;font-weight:700">
131 {s.count}
132 </div>
133 <div
134 style="font-size:11px;color:var(--text-muted);text-transform:uppercase"
135 >
136 {s.ecosystem}
137 </div>
138 </div>
139 ))}
140 </div>
141
142 {Array.from(grouped.entries()).map(([ecosystem, list]) => (
143 <>
144 <h3 style="margin-top:20px">
145 {ecosystem}{" "}
146 <span
147 style="font-size:12px;color:var(--text-muted);font-weight:400"
148 >
149 ({list.length})
150 </span>
151 </h3>
152 <div class="panel" style="margin-bottom:12px">
153 {list.map((d) => (
154 <div
155 class="panel-item"
156 style="justify-content:space-between;flex-wrap:wrap;gap:6px"
157 >
158 <div>
159 <span
160 style="font-family:var(--font-mono);font-weight:600"
161 >
162 {d.name}
163 </span>
164 {d.versionSpec && (
165 <span
166 style="margin-left:8px;font-size:12px;color:var(--text-muted);font-family:var(--font-mono)"
167 >
168 {d.versionSpec}
169 </span>
170 )}
171 {d.isDev && (
172 <span
173 style="margin-left:8px;font-size:10px;padding:2px 6px;background:var(--bg-subtle);border-radius:3px;color:var(--text-muted);text-transform:uppercase"
174 >
175 dev
176 </span>
177 )}
178 </div>
179 <a
180 href={`/${ownerName}/${repoName}/blob/HEAD/${d.manifestPath}`}
181 style="font-size:12px;color:var(--text-muted);font-family:var(--font-mono)"
182 >
183 {d.manifestPath}
184 </a>
185 </div>
186 ))}
187 </div>
188 </>
189 ))}
190 </>
191 )}
192 </div>
193 </Layout>
194 );
195});
196
197// ---------- Reindex (owner-only) ----------
198
199deps.post("/:owner/:repo/dependencies/reindex", requireAuth, async (c) => {
200 const user = c.get("user");
201 const { owner: ownerName, repo: repoName } = c.req.param();
202 const ctx = await loadRepo(ownerName, repoName);
203 if (!ctx) return c.notFound();
204 const { repo } = ctx;
205 if (!user || user.id !== repo.ownerId) {
206 return c.html(
207 <Layout title="Forbidden" user={user}>
208 <div class="empty-state">
209 <h2>403</h2>
210 <p>Only the repository owner can reindex dependencies.</p>
211 </div>
212 </Layout>,
213 403
214 );
215 }
216
217 const result = await indexRepositoryDependencies(repo.id);
218 const to = `/${ownerName}/${repoName}/dependencies`;
219 if (!result) {
220 return c.redirect(
221 `${to}?error=${encodeURIComponent(
222 "Reindex failed — is the default branch empty?"
223 )}`
224 );
225 }
226 return c.redirect(
227 `${to}?message=${encodeURIComponent(
228 `Indexed ${result.indexed} dependencies across ${result.manifests} manifests.`
229 )}`
230 );
231});
232
233export default deps;