import { Octokit } from "@octokit/rest"; import slugify from "@sindresorhus/slugify"; import { readFile } from "fs-extra"; import { safeLoad } from "js-yaml"; import { join } from "path"; export const generateSummary = async () => { const config = safeLoad( await readFile(join(".", ".statusrc.yml"), "utf8") ) as { sites: { name: string; url: string }[]; owner: string; repo: string; userAgent?: string; PAT?: string; assignees?: string[]; }; const owner = config.owner; const repo = config.repo; const octokit = new Octokit({ auth: config.PAT || process.env.GH_PAT || process.env.GITHUB_TOKEN, userAgent: config.userAgent || process.env.USER_AGENT || "KojBot", }); let readmeContent = await readFile(join(".", "README.md"), "utf8"); const startText = readmeContent.split("")[0]; const endText = readmeContent.split("")[1]; const pageStatuses: Array<{ url: string; status: string; slug: string; time: number; uptime: string; name: string; }> = []; let numberOfDown = 0; for await (const site of config.sites) { const slug = slugify(site.name); let startTime = new Date().toISOString(); try { startTime = (await readFile(join(".", "history", `${slug}.yml`), "utf8")) .split("\n") .find((line) => line.toLocaleLowerCase().includes("- starttime")) ?.split("startTime:")[1] .trim() || new Date().toISOString(); } catch (error) {} let secondsDown = 0; const history = await octokit.repos.listCommits({ owner, repo, path: `history/${slug}.yml`, per_page: 100, }); const issues = await octokit.issues.listForRepo({ owner, repo, labels: slug, filter: "all", per_page: 100, }); issues.data.forEach((issue) => { if (issue.closed_at) secondsDown += Math.floor( (new Date(issue.closed_at).getTime() - new Date(issue.created_at).getTime()) / 1000 ); else secondsDown += Math.floor( (new Date().getTime() - new Date(issue.created_at).getTime()) / 1000 ); }); const uptime = ( 100 - 100 * (secondsDown / ((new Date().getTime() - new Date(startTime).getTime()) / 1000)) ).toFixed(2); if (!history.data.length) continue; const averageTime = history.data .filter( (item) => item.commit.message.includes(" in ") && Number(item.commit.message.split(" in ")[1].split("ms")[0]) !== 0 && !isNaN(Number(item.commit.message.split(" in ")[1].split("ms")[0])) ) .map((item) => Number(item.commit.message.split(" in ")[1].split("ms")[0]) ) .reduce((p, c) => p + c, 0) / history.data.length; const status = history.data[0].commit.message.split(" ")[0].includes("🟩") ? "up" : "down"; pageStatuses.push({ name: site.name, url: site.url, slug, status, uptime, time: Math.floor(averageTime), }); if (status === "down") { numberOfDown++; } } if (readmeContent.includes("")) { readmeContent = `${startText} | URL | Status | History | Response Time | Uptime | | --- | ------ | ------- | ------------- | ------ | ${pageStatuses .map( (page) => `| ${ page.url.startsWith("$") ? page.name : `[${page.name}](${page.url})` } | ${page.status === "up" ? "🟩 Up" : "🟥 Down"} | [${ page.slug }.yml](https://github.com/${owner}/${repo}/commits/master/history/${ page.slug }.yml) | Response time graph ${page.time}ms | ${page.uptime}%` ) .join("\n")} ${endText}`; } // Add live status line readmeContent = readmeContent .split("\n") .map((line) => { if (line.includes("")) { line = `Live status: **${ numberOfDown === 0 ? "🟩 All systems operational" : numberOfDown === config.sites.length ? "🟥 Complete outage" : "🟨 Partial outage" }**`; } return line; }) .join("\n"); const sha = ( await octokit.repos.getContent({ owner, repo, path: "README.md", }) ).data.sha; await octokit.repos.createOrUpdateFileContents({ owner, repo, path: "README.md", message: ":pencil: Update summary in README [skip ci]", content: Buffer.from(readmeContent).toString("base64"), sha, }); let summarySha: string | undefined = undefined; try { summarySha = ( await octokit.repos.getContent({ owner, repo, path: "history/summary.json", }) ).data.sha; } catch (error) {} await octokit.repos.createOrUpdateFileContents({ owner, repo, path: "history/summary.json", message: ":card_file_box: Update status summary [skip ci]", content: Buffer.from(JSON.stringify(pageStatuses, null, 2)).toString( "base64" ), sha: summarySha, }); };