Skip to content
47 linesCodeBlameRaw
1/**
2 * A workflow's status badge: an SVG saying how its newest finished run went,
3 * on `?branch=` (the default branch, else any) and `?event=` when given.
4 * Anyone may load a public repository's; a private repository's needs
5 * someone who can see it, and is never cached where others could read it.
6 */
7import type { Route } from "./+types/actions-badge";
8import { badgeState, badgeSvg } from "../../lib/badge";
9import { actions, repos } from "../../lib/services.server";
10import { getViewer } from "../../lib/session.server";
11
12function svg(body: string, isPrivate: boolean, status = 200): Response {
13 return new Response(body, {
14 status,
15 headers: {
16 "content-type": "image/svg+xml; charset=utf-8",
17 // Shown in READMEs elsewhere, which fetch it through their own caches.
18 "cache-control": isPrivate ? "private, no-store" : "public, max-age=60, s-maxage=60",
19 "x-content-type-options": "nosniff",
20 "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
21 },
22 });
23}
24
25export async function loader({ params, context, request }: Route.LoaderArgs) {
26 const viewer = getViewer(context);
27 const path = { namespace: params.owner, name: params.repo };
28 const query = new URL(request.url).searchParams;
29 const branch = query.get("branch")?.trim() || undefined;
30 const event = query.get("event")?.trim() || undefined;
31 const repo = await repos.get(path, viewer).catch(() => null);
32 // Not there, or not this viewer's to see: the same answer either way.
33 if (!repo?.ok) return new Response("Not found.\n", { status: 404, headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" } });
34 const isPrivate = repo.value.isPrivate;
35 const workflows = await actions.workflows(path, viewer);
36 const file = params.file;
37 const workflow = workflows.ok ? workflows.value.find((w) => w.path.endsWith(`/${file}`) || w.id === file) : undefined;
38 if (!workflow) return svg(badgeSvg(file, "no status"), isPrivate, 404);
39 const runsOn = async (onBranch: string | undefined) => {
40 const runs = await actions.runs(path, viewer, { workflow: workflow.id, branch: onBranch, event, limit: 20 });
41 return runs.ok ? runs.value : [];
42 };
43 // The default branch unless one is asked for; any branch when it has none.
44 let runs = await runsOn(branch ?? repo.value.defaultBranch);
45 if (!branch && !runs.some((run) => run.status === "completed")) runs = await runsOn(undefined);
46 return svg(badgeSvg(workflow.name, badgeState(runs)), isPrivate);
47}