90 lines · 4.5 KB
import { repoDb } from "../scoped-db";
import { requestCredential } from "../token-scope";
import { loadRepo, repoPermission } from "../repos";
import { limitBrowse } from "../limits";
import { HttpError } from "../errors";
import { WORKFLOW_DIR } from "./spec";

const BADGE_PATH = /^\/([^/]+)\/([^/]+)\/actions\/workflows\/([^/]+\.ya?ml)\/badge\.svg$/;
const CHAR_WIDTH = 6.6;
const PADDING = 10;
const PUBLIC_MAX_AGE = 60;

const STATES: Record<string, { text: string; color: string }> = {
  success: { text: "passing", color: "#2ea043" },
  failure: { text: "failing", color: "#da3633" },
  cancelled: { text: "cancelled", color: "#6e7681" },
  skipped: { text: "skipped", color: "#6e7681" },
  none: { text: "no status", color: "#6e7681" },
};

function escapeXml(value: string): string {
  return value.replace(/[<>&"']/g, (char) => `&#${char.charCodeAt(0)};`);
}

export function badgeSvg(label: string, state: string): string {
  const status = STATES[state] ?? STATES.none;
  const left = Math.round(label.length * CHAR_WIDTH + PADDING * 2);
  const right = Math.round(status.text.length * CHAR_WIDTH + PADDING * 2);
  const width = left + right;
  const text = (x: number, value: string) =>
    `<text x="${x}" y="15" fill="#010101" fill-opacity=".3">${value}</text><text x="${x}" y="14">${value}</text>`;
  return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="20" role="img" aria-label="${escapeXml(label)}: ${status.text}">
<title>${escapeXml(label)}: ${status.text}</title>
<linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient>
<clipPath id="r"><rect width="${width}" height="20" rx="3" fill="#fff"/></clipPath>
<g clip-path="url(#r)"><rect width="${left}" height="20" fill="#24292f"/><rect x="${left}" width="${right}" height="20" fill="${status.color}"/><rect width="${width}" height="20" fill="url(#s)"/></g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">${text(left / 2, escapeXml(label))}${text(left + right / 2, status.text)}</g>
</svg>`;
}