Skip to content

g1t/apps/web/app/routes/repo/security-updates.tsx

66 lines3,168 bytesCodeBlame
1import { ExternalLink } from "lucide-react";
2
3import type { Route } from "./+types/security-updates";
4import { page } from "../../lib/meta";
5import { DependencyUpdates } from "../../components/dependency-updates";
6import { SectionHeader } from "../../components/security-suite";
7import { DEPENDENCY_UPDATES_DOCS } from "../../lib/dependency-updates";
8import { security } from "../../lib/services.server";
9import { assertSameOrigin, getViewer, requireUser, unwrap } from "../../lib/session.server";
10import { refusal, requireInsider } from "../../lib/access.server";
11
12export function meta({ params, ...args }: Route.MetaArgs) {
13 return page(args, { title: `Dependency updates · ${params.owner}/${params.repo} · g1t` });
14}
15
16export async function loader({ params, context, request }: Route.LoaderArgs) {
17 const viewer = getViewer(context) ?? requireUser(context, request);
18 // As the rest of the Security section: Write and up.
19 const { access } = await requireInsider(context, params, "push");
20 const overview = unwrap(await security.overview({ namespace: params.owner, name: params.repo }, viewer));
21 return { updates: overview.versionUpdates, can: access.can };
22}
23
24export async function action({ params, context, request }: Route.ActionArgs) {
25 assertSameOrigin(request);
26 const user = requireUser(context, request);
27 const form = await request.formData();
28 if (String(form.get("intent") ?? "") !== "check_updates") return { ok: false, error: "Unknown action." };
29 // Checking for updates now takes Write, as pushing does.
30 const refused = await refusal(context, params, "push");
31 if (refused) return { ok: false, error: refused };
32 const checked = await security.checkUpdates(user, { namespace: params.owner, name: params.repo }, String(form.get("entry") ?? ""));
33 return checked.ok ? { ok: true } : { ok: false, error: checked.error.message };
34}
35
36/** The dependency update file g1t reads, each entry's schedule, and the pull requests it opened. */
37export default function SecurityDependencyUpdates({ loaderData, params }: Route.ComponentProps) {
38 const { updates, can } = loaderData;
39 const base = `/${params.owner}/${params.repo}`;
40 return (
41 <div className="max-w-5xl space-y-6">
42 <SectionHeader
43 title="Dependency updates"
44 about={
45 <>
46 Pull requests that keep dependencies current on a schedule, from a <code className="text-fg-soft">dependabot.yml</code>{" "}
47 file (version 2) in <code className="text-fg-soft">.g1t/</code> or <code className="text-fg-soft">.github/</code>.
48 Security updates follow the same file.
49 </>
50 }
51 actions={
52 <a
53 href={DEPENDENCY_UPDATES_DOCS}
54 target="_blank"
55 rel="noreferrer"
56 className="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm hover:border-line-strong"
57 >
58 How to write the file
59 <ExternalLink size={12} />
60 </a>
61 }
62 />
63 <DependencyUpdates state={updates} action={`${base}/security/dependency-updates`} base={base} canCheck={can.push} titled={false} />
64 </div>
65 );
66}