import slugify from "@sindresorhus/slugify" ; import { mkdirp , readdir , readFile , writeFile } from "fs-extra" ; import { join } from "path" ; import { format } from "prettier" ; import { getResponseTimeForSite } from "./helpers/calculate-response-time" ; import { getUptimePercentForSite } from "./helpers/calculate-uptime" ; import { getConfig } from "./helpers/config" ; import { commit , push } from "./helpers/git" ; import { getOctokit } from "./helpers/github" ; import { shouldContinue } from "./helpers/init-check" ; import { SiteStatus } from "./interfaces" ; import { parse } from "url" ; import { getOwnerRepo } from "./helpers/secrets" ; export const generateSummary = async ( ) => { if ( ! ( await shouldContinue ( ) ) ) return ; await mkdirp ( "history" ) ; const [ owner , repo ] = getOwnerRepo ( ) ; const config = await getConfig ( ) ; const octokit = await getOctokit ( ) ; let readmeContent = await readFile ( join ( "." , "README.md" ) , "utf8" ) ; const startText = readmeContent . split ( config . summaryStartHtmlComment || "<!--start: status pages-->" ) [ 0 ] ; const endText = readmeContent . split ( config . summaryEndHtmlComment || "<!--end: status pages-->" ) [ 1 ] ; const pageStatuses = [ ] ; let numberOfDown = 0 ; let numberOfDegraded = 0 ; for await ( const site of config . sites ) { const slug = site . slug || slugify ( site . name ) ; const uptimes = await getUptimePercentForSite ( slug ) ; console . log ( "Uptimes" , uptimes ) ; const responseTimes = await getResponseTimeForSite ( slug ) ; console . log ( "Response times" , responseTimes ) ; let fallbackIcon = "" ; try { fallbackIcon = `https://favicons.githubusercontent.com/${ parse ( site . url ) . hostname }` ; } catch ( error ) { } pageStatuses . push ( { name : site . name , url : site . url , icon : site . icon || fallbackIcon , slug , status : responseTimes . currentStatus , uptime : uptimes . all , uptimeDay : uptimes . day , uptimeWeek : uptimes . week , uptimeMonth : uptimes . month , uptimeYear : uptimes . year , time : Math . floor ( responseTimes . all ) , timeDay : responseTimes . day , timeWeek : responseTimes . week , timeMonth : responseTimes . month , timeYear : responseTimes . year , dailyMinutesDown : uptimes . dailyMinutesDown , } ) ; if ( responseTimes . currentStatus === "down" ) numberOfDown ++ ; if ( responseTimes . currentStatus === "degraded" ) numberOfDegraded ++ ; } let website = `https://${ config . owner }.github.io/${ config . repo }` ; if ( config [ "status-website" ] && config [ "status-website" ] . cname ) website = `https://${ config [ "status-website" ] . cname }` ; const i18n = config . i18n || { } ; if ( readmeContent . includes ( config . summaryStartHtmlComment || "<!--start: status pages-->" ) ) { readmeContent = `${ startText }${ config . summaryStartHtmlComment || "<!--start: status pages-->" }
<!-- This summary is generated by Upptime (https://github.com/upptime/upptime) -->
<!-- Do not edit this manually, your changes will be overwritten -->
<!-- prettier-ignore -->
| ${ i18n . url || "URL" } | ${ i18n . status || "Status" } | ${ i18n . history || "History" } | ${ i18n . responseTime || "Response Time" } | ${ i18n . uptime || "Uptime" } |
| --- | ------ | ------- | ------------- | ------ |
${ pageStatuses . map ( ( page ) => `| <img alt="" src="${ page . icon }" height="13"> ${ page . url . includes ( "$" ) ? page . name : `[${ page . name }](${ page . url })` } | ${ page . status === "up" ? i18n . up || "🟩 Up" : page . status === "degraded" ? i18n . degraded || "🟨 Degraded" : i18n . down || "🟥 Down" } | [${ page . slug }.yml](https://github.com/${ owner }/${ repo }/commits/HEAD/history/${ page . slug }.yml) | <details><summary><img alt="${ i18n . responseTimeGraphAlt || "Response time graph" }" src="./graphs/${ page . slug }/response-time-week.png" height="20"> ${ page . timeWeek }${ i18n . ms || "ms" }</summary><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . responseTime || "Response time" } ${ page . time }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fresponse-time.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . responseTimeDay || "24-hour response time" } ${ page . timeDay }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fresponse-time-day.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . responseTimeWeek || "7-day response time" } ${ page . timeWeek }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fresponse-time-week.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . responseTimeMonth || "30-day response time" } ${ page . timeMonth }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fresponse-time-month.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . responseTimeYear || "1-year response time" } ${ page . timeYear }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fresponse-time-year.json"></a></details> | <details><summary><a href="${ website }/history/${ page . slug }">${ page . uptimeWeek }</a></summary><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . uptime || "All-time uptime" } ${ page . uptime }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fuptime.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . uptimeDay || "24-hour uptime" } ${ page . uptimeDay }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fuptime-day.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . uptimeWeek || "7-day uptime" } ${ page . uptimeWeek }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fuptime-week.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . uptimeMonth || "30-day uptime" } ${ page . uptimeMonth }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fuptime-month.json"></a><br><a href="${ website }/history/${ page . slug }"><img alt="${ i18n . uptimeYear || "1-year uptime" } ${ page . uptimeYear }" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2F${ owner }%2F${ repo }%2FHEAD%2Fapi%2F${ page . slug }%2Fuptime-year.json"></a></details>` ) . join ( "\n" ) }
${ config . summaryEndHtmlComment || "<!--end: status pages-->" }${ endText }` ; } if ( `${ owner }/${ repo }` !== "upptime/upptime" ) { readmeContent = readmeContent . split ( "\n" ) . map ( ( line , index ) => { if ( index === 0 && line . includes ( "https://upptime.js.org" ) ) return `# [📈 ${ i18n . liveStatus || "Live Status" }](${ website }): ${ i18n . liveStatusHtmlComment || "<!--live status-->" } **${ i18n . allSystemsOperational || "🟩 All systems operational" }**` ; if ( line . includes ( "[![Summary CI](https://github.com" ) && readmeContent . includes ( "<!--start: description-->" ) ) return `${ line }\n\nWith [Upptime](https://upptime.js.org), you can get your own unlimited and free uptime monitor and status page, powered entirely by a GitHub repository. We use [Issues](https://github.com/${ config . owner }/${ config . repo }/issues) as incident reports, [Actions](https://github.com/${ config . owner }/${ config . repo }/actions) as uptime monitors, and [Pages](${ website }) for the status page.` ; return line ; } ) . filter ( ( line ) => ! line . startsWith ( `## [📈 ${ i18n . liveStatus || "Live Status" }]` ) ) . join ( "\n" ) ; const docsStartText = readmeContent . split ( "<!--start: docs-->" ) [ 0 ] ; const docsEndText = readmeContent . split ( "<!--end: docs-->" ) [ 1 ] ; if ( readmeContent . includes ( "<!--start: docs-->" ) ) readmeContent = `${ docsStartText }[**Visit our status website →**](${ website })${ docsEndText }` ; const logoStartText = readmeContent . split ( "<!--start: logo-->" ) [ 0 ] ; const logoEndText = readmeContent . split ( "<!--end: logo-->" ) [ 1 ] ; if ( readmeContent . includes ( "<!--start: logo-->" ) ) readmeContent = `${ logoStartText }${ logoEndText }` ; let name = `[${ config . owner }](${ website })` ; if ( readmeContent . includes ( "[MIT](./LICENSE) © [Koj](https://koj.co)" ) || readmeContent . includes ( "<!--start: description-->" ) ) { try { const org = await octokit . users . getByUsername ( { username : config . owner } ) ; name = `[${ org . data . name || config . owner }](${ org . data . blog || website })` ; } catch ( error ) { } const descriptionStartText = readmeContent . split ( "<!--start: description-->" ) [ 0 ] ; const descriptionEndText = readmeContent . split ( "<!--end: description-->" ) [ 1 ] ; if ( readmeContent . includes ( "<!--start: description-->" ) ) readmeContent = `${ descriptionStartText }This repository contains the open-source uptime monitor and status page for ${ name }, powered by [Upptime](https://github.com/upptime/upptime).${ descriptionEndText }` ; readmeContent = readmeContent . replace ( "[MIT](./LICENSE) © [Koj](https://koj.co)" , `[MIT](./LICENSE) © ${ name }` ) ; if ( ! config . skipPoweredByReadme ) { readmeContent = readmeContent . replace ( "## 📄 License\n\n- Code: [MIT](./LICENSE)" , "## 📄 License\n\n- Powered by: [Upptime](https://github.com/upptime/upptime)\n- Code: [MIT](./LICENSE)" ) ; } } readmeContent = readmeContent . replace ( new RegExp ( "upptime/upptime/(workflows|actions)" , "g" ) , `${ config . owner }/${ config . repo }/$1` ) ; try { const repoInfo = await octokit . repos . get ( { owner , repo } ) ; if ( ! repoInfo . data . description && ! config . skipDescriptionUpdate ) await octokit . repos . update ( { owner , repo , description : `📈 Uptime monitor and status page for ${ name . split ( "]" ) [ 0 ] . replace ( "[" , "" ) }, powered by @upptime` , } ) ; console . log ( "Current topics are" , repoInfo . data . topics ) ; if ( ! ( repoInfo . data . topics || [ ] ) . includes ( "upptime" ) && ! config . skipTopicsUpdate ) await octokit . repos . replaceAllTopics ( { owner , repo , names : [ ... ( repoInfo . data . topics || [ ] ) , "uptime-monitor" , "status-page" , "upptime" , ] . filter ( ( value , index , array ) => array . indexOf ( value ) === index ) , } ) ; console . log ( "Possibly updated to to" , [ ... ( repoInfo . data . topics || [ ] ) , "uptime-monitor" , "status-page" , "upptime" ] . filter ( ( value , index , array ) => array . indexOf ( value ) === index ) ) ; console . log ( "Topics are" , ( await octokit . repos . get ( { owner , repo } ) ) . data . topics ) ; if ( ! repoInfo . data . homepage && ! config . skipHomepageUpdate ) await octokit . repos . update ( { owner , repo , homepage : website , } ) ; } catch ( error ) { console . log ( error ) ; } } readmeContent = readmeContent . split ( "\n" ) . map ( ( line ) => { if ( line . includes ( "<!--live status-->" ) ) { line = `${ line . split ( "<!--live status-->" ) [ 0 ] }<!--live status--> **${ numberOfDown === 0 ? numberOfDegraded === 0 ? i18n . allSystemsOperational || "🟩 All systems operational" : i18n . degradedPerformance || "🟨 Degraded performance" : numberOfDown === config . sites . length ? i18n . completeOutage || "🟥 Complete outage" : i18n . partialOutage || "🟧 Partial outage" }**` ; } return line ; } ) . join ( "\n" ) ; await writeFile ( join ( "." , "README.md" ) , format ( readmeContent , { parser : "markdown" } ) ) ; commit ( ( config . commitMessages || { } ) . readmeContent || ":pencil: Update summary in README [skip ci] [upptime]" , ( config . commitMessages || { } ) . commitAuthorName , ( config . commitMessages || { } ) . commitAuthorEmail ) ; const workflows = ( await readdir ( join ( "." , ".github" , "workflows" ) ) ) . filter ( ( i ) => i . endsWith ( ".yml" ) ) ; for await ( const workflow of workflows ) { const content = await readFile ( join ( "." , ".github" , "workflows" , workflow ) , "utf8" ) ; const newContent = content . replace ( "actions/setup-node@v2.1.1" , "actions/setup-node@v1.4.4" ) ; if ( content !== newContent ) { console . log ( "Updating workflow" , workflow ) ; await writeFile ( join ( "." , ".github" , "workflows" , workflow ) , newContent ) ; } } await writeFile ( join ( "." , "history" , "summary.json" ) , JSON . stringify ( pageStatuses , null , 2 ) ) ; commit ( ( config . commitMessages || { } ) . summaryJson || ":card_file_box: Update status summary [skip ci] [upptime]" , ( config . commitMessages || { } ) . commitAuthorName , ( config . commitMessages || { } ) . commitAuthorEmail ) ; push ( ) ; if ( ! config . skipDeleteIssues ) { const issuesRecentlyClosed = await octokit . issues . listForRepo ( { owner , repo , state : "closed" , labels : "status" , per_page : 10 , } ) ; console . log ( "Found recently closed issues" , issuesRecentlyClosed . data . length ) ; for await ( const issue of issuesRecentlyClosed . data ) { if ( issue . closed_at && new Date ( issue . closed_at ) . getTime ( ) - new Date ( issue . created_at ) . getTime ( ) < 900000 && issue . comments === 1 ) { try { console . log ( "Trying to delete issue" , issue . number , issue . node_id ) ; const result = await octokit . graphql ( `
      mutation deleteIssue {
        deleteIssue(input:{issueId:"${ issue . node_id }"}) {
          clientMutationId
        }
      }` ) ; console . log ( "Success" , result ) ; } catch ( error ) { console . log ( "Error deleting this issue" , error ) ; } } } } } ;