Skip to content
427 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1/**
2 * The dependency update file, as a project's Security page shows it: which
3 * file was read, its problems with their lines, each `updates` entry with
4 * its schedule and last check, the private registries, the update pull
5 * requests g1t has open, and ignore conditions set in comments. "Check for
6 * updates" posts the `check_updates` intent to its page, the Security
7 * section's Dependency updates tab (`routes/repo/security-updates.tsx`).
8 */
9import {
10 CalendarClock,
11 CircleCheck,
12 CircleDot,
13 ExternalLink,
14 FileCode,
15 FileWarning,
16 GitMerge,
17 GitPullRequest,
18 Info,
19 KeyRound,
20 Loader,
21 RefreshCw,
22 Wrench,
23 EyeOff,
24} from "lucide-react";
25import { Link, useFetcher } from "react-router";
26
27import type { UpdatePull, VersionUpdateEntry, VersionUpdatesState } from "@g1t/contracts";
28
29import {
30 DEPENDENCY_UPDATES_DOCS,
31 PULL_STATES,
32 conditionText,
33 entryStatus,
34 groupText,
35 ignoreText,
36 livePulls,
37 timeUntil,
38 utc,
39} from "../lib/dependency-updates";
40import { TimeAgo } from "./ui";
41import { Badge, type BadgeTone } from "./ui/badge";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.42import { Button } from "./ui/button";
43import { Card } from "./ui/card";
Merge branch 'worktree-agent-ae1299e92e4462012'44import { Hint } from "./ui/hint";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar45
46type Done = { ok: boolean; error?: string } | undefined;
47
48const PULL_TONES: Record<UpdatePull["state"], BadgeTone> = {
49 requested: "info",
50 open: "accent",
51 merged: "merged",
52 closed: "neutral",
53 superseded: "neutral",
54 needs_code: "warn",
55 failed: "danger",
56};
57
58/** Text with `code` spans, as the service writes its messages. */
59function Said({ text }: { text: string }) {
60 return (
61 <>
62 {text.split("`").map((part, index) =>
63 index % 2 === 1 ? (
64 <code key={index} className="text-fg-soft">
65 {part}
66 </code>
67 ) : (
68 part
69 ),
70 )}
71 </>
72 );
73}
74
75function Heading({ children }: { children: React.ReactNode }) {
76 return <h4 className="text-xs font-medium tracking-wide text-faint uppercase">{children}</h4>;
77}
78
79/** One `updates` entry: what it covers, when it runs, what it found. */
80function Entry({ entry, action, canCheck }: { entry: VersionUpdateEntry; action: string; canCheck: boolean }) {
81 const check = useFetcher<Done>();
82 const status = entryStatus(entry);
83 const checking = check.state !== "idle";
84 const rules: { label: string; values: string[] }[] = [
85 { label: "Groups", values: entry.groups.map(groupText) },
86 { label: "Ignored", values: entry.ignore.map(ignoreText) },
87 {
88 label: "Allowed",
89 values: entry.allow.map((rule) =>
90 [rule.dependency, rule.dependencyType, ...rule.updateTypes.map((type) => type.replace("version-update:semver-", ""))]
91 .filter(Boolean)
92 .join(" · "),
93 ),
94 },
95 { label: "Assignees", values: entry.assignees },
96 { label: "Reviewers", values: entry.reviewers },
97 ].filter((rule) => rule.values.length > 0);
98 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.99 <Card asChild tone="plain" radius="lg" className="p-3">
100 <li>
101 <div className="flex flex-wrap items-start justify-between gap-3">
102 <div className="min-w-0">
103 <p className="flex flex-wrap items-center gap-2 text-sm">
104 <span className="font-mono font-medium text-fg">{entry.ecosystem}</span>
105 <span className="font-mono text-muted wrap-anywhere">{entry.directories.join(", ")}</span>
106 {entry.targetBranch && <span className="font-mono text-xs text-faint">→ {entry.targetBranch}</span>}
107 <Badge tone={status.tone}>{status.label}</Badge>
108 </p>
109 <p className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted">
110 {entry.schedule && (
111 <span className="inline-flex items-center gap-1">
112 <CalendarClock size={12} className="shrink-0" />
113 {entry.schedule}
114 </span>
115 )}
116 {entry.nextRunAt && (
117 <Hint label={utc(entry.nextRunAt)}>
118 <span>Next check {timeUntil(entry.nextRunAt)}</span>
119 </Hint>
120 )}
121 <span>
122 {entry.lastCheckedAt ? (
123 <>
124 Last checked <TimeAgo at={entry.lastCheckedAt} />
125 </>
126 ) : (
127 "Not checked yet"
128 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar129 </span>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.130 {entry.supported && <span>Up to {entry.openPullRequestsLimit} open</span>}
131 </p>
132 </div>
133 {entry.supported && canCheck && (
134 <check.Form method="post" action={action} className="shrink-0">
135 <input type="hidden" name="intent" value="check_updates" />
136 <input type="hidden" name="entry" value={entry.id} />
137 <Button
138 type="submit"
139 disabled={checking || entry.openPullRequestsLimit === 0}
140 variant="outline"
141 size="inline"
142 className="px-2.5 py-1 text-xs hover:bg-raised font-normal"
143 >
144 <RefreshCw size={12} className={checking ? "animate-spin" : ""} />
145 {checking ? "Checking…" : "Check for updates"}
146 </Button>
147 </check.Form>
148 )}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar149 </div>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.150 {check.data?.error && <p className="mt-2 text-xs text-danger">{check.data.error}</p>}
151 {entry.lastError ? (
152 <p className="mt-2 flex items-start gap-1.5 text-xs text-danger">
153 <FileWarning size={12} className="mt-0.5 shrink-0" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar154 <span className="wrap-anywhere">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.155 <Said text={entry.lastError} />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar156 </span>
157 </p>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.158 ) : (
159 entry.lastResult && (
160 <p className="mt-2 flex items-start gap-1.5 text-xs text-muted">
161 <CircleCheck size={12} className="mt-0.5 shrink-0 text-success" />
162 <span className="wrap-anywhere">
163 <Said text={entry.lastResult} />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar164 </span>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.165 </p>
166 )
167 )}
168 {rules.length > 0 && (
169 <dl className="mt-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs">
170 {rules.map((rule) => (
171 <div key={rule.label} className="contents">
172 <dt className="text-faint">{rule.label}</dt>
173 <dd className="font-mono text-muted wrap-anywhere">{rule.values.join("; ")}</dd>
174 </div>
175 ))}
176 </dl>
177 )}
178 {entry.notes.length > 0 && (
179 <ul className="mt-2 space-y-1">
180 {entry.notes.map((note) => (
181 <li key={note} className="flex items-start gap-1.5 text-xs text-faint">
182 <Info size={12} className="mt-0.5 shrink-0" />
183 <span>
184 <Said text={note} />
185 </span>
186 </li>
187 ))}
188 </ul>
189 )}
190 </li>
191 </Card>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar192 );
193}
194
195/** One update pull request g1t has open or is making. */
196function PullRow({ pull, base }: { pull: UpdatePull; base: string }) {
197 const icon =
198 pull.state === "needs_code" ? (
199 <Wrench size={13} className="text-warn" />
200 ) : pull.state === "requested" ? (
201 <Loader size={13} className="text-info" />
202 ) : (
Merge branch 'worktree-agent-ad7c6d88d93adc817'203 <GitPullRequest size={13} className="text-success" />
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar204 );
205 const count = pull.dependencies.length;
206 return (
207 <li className="flex items-start gap-2 py-2">
208 <span className="mt-0.5 shrink-0">{icon}</span>
209 <div className="min-w-0 flex-1">
210 <p className="text-sm">
211 {pull.pull ? (
212 <Link to={`${base}/pulls/${pull.pull}`} className="hover:underline">
213 {pull.title} <span className="text-muted">#{pull.pull}</span>
214 </Link>
215 ) : (
216 pull.title
217 )}
218 </p>
219 <p className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted">
220 <Badge tone={PULL_TONES[pull.state]}>{PULL_STATES[pull.state]}</Badge>
221 <span>{pull.kind === "security" ? "Security update" : "Version update"}</span>
222 <span>
223 {count} {count === 1 ? "dependency" : "dependencies"}
224 </span>
225 {pull.mergeRequestedBy && (
226 <span className="inline-flex items-center gap-1">
227 <GitMerge size={11} />
228 Merges when its checks pass, as @{pull.mergeRequestedBy} asked
229 </span>
230 )}
231 </p>
232 {pull.error && <p className="mt-0.5 text-xs text-danger wrap-anywhere">{pull.error}</p>}
233 </div>
234 </li>
235 );
236}
237
238export function DependencyUpdates({
239 state,
240 action,
241 base,
242 canCheck,
243 titled = true,
244}: {
245 state: VersionUpdatesState;
246 action: string;
247 base: string;
248 canCheck: boolean;
249 /** Its own title and what it is for; off where the page says so above it. */
250 titled?: boolean;
251}) {
252 const pulls = livePulls(state.pulls);
253 const fileLink = state.path && state.commit ? `${base}/blob/${state.commit}/${state.path}` : null;
254 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.255 <Card className="p-4">
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar256 {titled && (
257 <>
258 <div className="flex flex-wrap items-center justify-between gap-2">
259 <span className="text-sm font-medium">Dependency updates</span>
260 <a
261 href={DEPENDENCY_UPDATES_DOCS}
262 target="_blank"
263 rel="noreferrer"
264 className="inline-flex items-center gap-1 text-xs text-fg-soft hover:text-fg hover:underline"
265 >
266 How to write the file
267 <ExternalLink size={10} />
268 </a>
269 </div>
270 <p className="mt-1 text-sm text-muted">
271 Pull requests that keep dependencies current on a schedule, from a <code className="text-fg-soft">dependabot.yml</code>{" "}
272 file (version 2) in <code className="text-fg-soft">.g1t/</code> or <code className="text-fg-soft">.github/</code>. Security
273 updates follow the same file.
274 </p>
275 </>
276 )}
277
278 <div className={`${titled ? "mt-3 " : ""}flex items-start gap-1.5 text-xs text-muted`}>
279 {state.found && state.path ? (
280 <>
281 {state.problems.length > 0 ? (
282 <FileWarning size={13} className="shrink-0 text-danger" />
283 ) : (
284 <FileCode size={13} className="shrink-0 text-accent" />
285 )}
286 <span>
287 Reading{" "}
288 {fileLink ? (
289 <Link to={fileLink} className="font-mono text-fg-soft hover:underline">
290 {state.path}
291 </Link>
292 ) : (
293 <span className="font-mono text-fg-soft">{state.path}</span>
294 )}
295 {state.readAt && (
296 <>
297 , read <TimeAgo at={state.readAt} />
298 </>
299 )}
300 {state.problems.length === 0 && (
301 <>
302 : {state.updates.length} {state.updates.length === 1 ? "entry" : "entries"}
303 </>
304 )}
305 </span>
306 </>
307 ) : (
308 <>
309 <CircleDot size={13} className="shrink-0 text-faint" />
310 <span>
311 No <code>dependabot.yml</code> on the default branch.
312 </span>
313 </>
314 )}
315 </div>
316 {state.ignoredPaths.length > 0 && (
317 <p className="mt-1 flex items-start gap-1.5 text-xs text-warn">
318 <Info size={12} className="mt-0.5 shrink-0" />
319 <span>
320 Also found {state.ignoredPaths.map((path) => <code key={path}>{path} </code>)}
321 and ignored it: the file under <code>.g1t/</code> is read in its place.
322 </span>
323 </p>
324 )}
325
326 {state.problems.length > 0 && (
327 <div className="mt-3 rounded-lg border border-danger/40 bg-danger/5 p-3">
328 <p className="text-xs font-medium text-danger">
329 {state.problems.length === 1 ? "1 problem" : `${state.problems.length} problems`}. Nothing is updated from this file until
330 {state.problems.length === 1 ? " it is" : " they are"} fixed.
331 </p>
332 <ul className="mt-2 space-y-1 text-xs">
333 {state.problems.map((problem) => (
334 <li key={`${problem.line}:${problem.column}:${problem.key}:${problem.message}`} className="flex gap-2">
335 <span className="w-14 shrink-0 font-mono text-faint tabular-nums">
336 {problem.line > 0 ? `Line ${problem.line}` : "File"}
337 </span>
338 <span className="min-w-0 text-fg/90 wrap-anywhere">
339 {problem.key && <code className="mr-1 text-fg-soft">{problem.key}</code>}
340 <Said text={problem.message} />
341 </span>
342 </li>
343 ))}
344 </ul>
345 </div>
346 )}
347
348 {state.updates.length > 0 && (
349 <section className="mt-4">
350 <Heading>Entries</Heading>
351 <ul className="mt-2 space-y-2">
352 {state.updates.map((entry) => (
353 <Entry key={entry.id} entry={entry} action={action} canCheck={canCheck && state.problems.length === 0} />
354 ))}
355 </ul>
356 </section>
357 )}
358
359 {state.registries.length > 0 && (
360 <section className="mt-4">
361 <Heading>Private registries</Heading>
362 <ul className="mt-2 divide-y divide-line/60 text-xs">
363 {state.registries.map((registry) => (
364 <li key={registry.name} className="flex flex-wrap items-center gap-x-3 gap-y-0.5 py-1.5">
365 <span className="font-mono text-fg-soft">{registry.name}</span>
366 <span className="text-faint">{registry.kind}</span>
367 <span className="min-w-0 font-mono text-muted wrap-anywhere">{registry.url}</span>
368 {registry.secrets.length > 0 && (
369 <span className="inline-flex items-center gap-1 text-muted">
370 <KeyRound size={11} />
371 {registry.secrets.join(", ")}
372 </span>
373 )}
374 </li>
375 ))}
376 </ul>
377 </section>
378 )}
379
380 <section className="mt-4">
381 <Heading>Update pull requests</Heading>
382 {pulls.length > 0 ? (
383 <ul className="mt-1 divide-y divide-line/60">
384 {pulls.map((pull) => (
385 <PullRow key={`${pull.branch}:${pull.updatedAt}`} pull={pull} base={base} />
386 ))}
387 </ul>
388 ) : (
389 <p className="mt-2 text-xs text-muted">None open.</p>
390 )}
391 </section>
392
393 {state.ignores.length > 0 && (
394 <section className="mt-4">
395 <Heading>Ignored in comments</Heading>
396 <ul className="mt-2 space-y-1 text-xs">
397 {state.ignores.map((condition) => (
398 <li
399 key={`${condition.ecosystem}:${condition.dependency}:${conditionText(condition)}`}
400 className="flex flex-wrap items-center gap-x-2 gap-y-0.5"
401 >
402 <EyeOff size={11} className="shrink-0 text-faint" />
403 <span className="font-mono text-fg-soft">{condition.dependency}</span>
404 <span className="text-muted">{conditionText(condition)}</span>
405 <span className="text-faint">
406 {condition.ecosystem} · @{condition.by}
407 {condition.pull != null && (
408 <>
409 {" "}
410 in{" "}
411 <Link to={`${base}/pulls/${condition.pull}`} className="hover:underline">
412 #{condition.pull}
413 </Link>
414 </>
415 )}
416 </span>
417 </li>
418 ))}
419 </ul>
420 <p className="mt-2 text-xs text-faint">
421 Comment <code>@g1t unignore &lt;dependency&gt;</code> on an update pull request to undo one.
422 </p>
423 </section>
424 )}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.425 </Card>
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar426 );
427}

This file's history is long; its oldest lines are credited to the oldest commit read.