Skip to content

Commit

Permalink
Merge pull request #1838 from ndunnett/master
Browse files Browse the repository at this point in the history
Rewrite update-badge action
  • Loading branch information
Bogdanp committed Apr 7, 2024
2 parents b2a3fe4 + ea35c93 commit 44328ad
Show file tree
Hide file tree
Showing 16 changed files with 1,449 additions and 1,954 deletions.
8 changes: 2 additions & 6 deletions .github/actions/update-badge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ This action fetches last commit time of repositories and generates the badge for

**Required** GitHub token

### `inputFile`
### `fileNames`

**Required** File path of README.md

### `outputFile`

**Required** File path of updated README.md
**Required** Names of files to process (space delimited list)

## Outputs

Expand Down
12 changes: 4 additions & 8 deletions .github/actions/update-badge/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ inputs:
ghToken:
description: 'GitHub token'
required: true
inputFile:
description: 'File path of README.md'
fileNames:
description: 'Names of files to process (space delimited list)'
required: true
default: 'README.md'
outputFile:
description: 'File path of output'
required: true
default: 'README-badge.md'
runs:
using: 'node12'
main: 'index.js'
using: 'node20'
main: 'index.js'
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
// modifiy based on https://runkit.com/melnikow/version-from-requirements-txt
const axios = require('axios');
const moment = require('moment');
const moment = require('moment')

const badgeDefaults = {
schemaVersion: 1,
label: 'last commit',
message: 'not available',
color: 'red'
}

async function handle(octokit, { owner, repo }) {
let lastCommitDayStr
try {
const { data: commitList } = await Promise.race([
new Promise(resolve => setTimeout(resolve, 10 * 1000)),
octokit.repos.listCommits({
owner,
repo
})
])
lastCommitDayStr = commitList[0].commit.author.date
} catch (e) {
if (e.message.indexOf('rate limit exceeded') !== -1) {
throw e;
}
module.exports = function generateBadge (repo) {
const { label, message, color } = badgeProperties(repo)
return (
'https://img.shields.io/badge/' +
[label, message, color]
.map(s => encodeURIComponent(s.replace(/\-/g, '--')))
.join('-')
)
}

console.log(`[${owner }/${repo}] ${e.message}`)
return { message: 'resource not available', color: 'red' }
function badgeProperties (repo) {
if (repo.lastCommit === undefined) {
return badgeDefaults
} else {
const message = moment(repo.lastCommit).format('YYYY-MM-DD')
return Object.assign({}, badgeDefaults, { message, color: age(message) })
}
}

const message = moment(lastCommitDayStr).format('YYYY-MM-DD')
return {
message,
color: age(message),
function age (date) {
if (!date.add) {
return 'brightgreen'
}
}

module.exports = async function invokeHandler(octokit, query) {
return Object.assign({}, badgeDefaults, await handle(octokit, query))
const now = moment()
const then = moment(date)
if (now.diff(date) < 0) date.add(-1, 'y')

const daysElapsed = now.diff(moment(date), 'days')
const colorByAge = colorScale([0, 5, 10, 20, 25], undefined, false)
return colorByAge(daysElapsed)
}

// copy from https://github.com/badges/shields
function colorScale(steps, colors, reversed) {
function colorScale (steps, colors, reversed) {
if (steps === undefined) {
throw Error('When invoking colorScale, steps should be provided.')
}
Expand Down Expand Up @@ -78,17 +80,3 @@ function colorScale(steps, colors, reversed) {
return colors.slice(stepIndex)[0]
}
}

function age(date) {
if (!date.add) {
return "brightgreen"
}

const now = moment()
const then = moment(date)
if (now.diff(date) < 0) date.add(-1, 'y')

const daysElapsed = now.diff(moment(date), 'days')
const colorByAge = colorScale([0, 5, 10, 20, 25], undefined, false)
return colorByAge(daysElapsed)
}
135 changes: 74 additions & 61 deletions .github/actions/update-badge/index.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,87 @@
const fs = require('fs')
const core = require('@actions/core')
const github = require('@actions/github')
const endpoint = require('./endpoint')
const generateBadge = require('./generateBadge')
const updateRepos = require('./updateRepos')

let octokit
;(async () => {
const ghToken = core.getInput('ghToken')
const fileNames = core.getInput('fileNames').split(' ')
let octokit = github.getOctokit(ghToken)

(async function() {
for (const fileName of fileNames) {
await processFile(fileName, octokit)
}
})()

async function processFile (fileName, octokit) {
try {
const ghToken = core.getInput('ghToken')
const inputFile = core.getInput('inputFile')
const outputFile = core.getInput('outputFile')
const { pre, lines, post } = await core.group(
`Reading from '${fileName}'...`,
() => parseFile(fileName)
)

let repos = await core.group('Parsing repositories...', () =>
parseGithubRepos(lines)
)

repos = await core.group('Querying repositories...', async () => {
return await updateRepos(octokit, repos)
})

const content = fs.readFileSync(inputFile, 'utf8')
const lines = content.split('\n')
const repos = await core.group('Extracting repos...', () => extractRepositories(lines))
core.info(`count=${repos.length}`)
const preLines = pre.split('\n').length

octokit = github.getOctokit(ghToken)
await core.group('Fetching repositories & updating lines...', async () => {
await core.group('Updating badges...', async () => {
for (const repo of repos) {
const line = await generateLine(repo.repoStr)
if (otherDomain(lines[repo.index])) {
core.info(`...line refers to non-GH domain: '${lines[repo.index]}'`)
} else if (shouldUpdate(lines[repo.index], line)) {
lines[repo.index] = line
core.info(`...updated line: '${line}'`)
const line = generateLine(repo)

if (line === lines[repo.index]) {
core.info(`...skipped line ${repo.index + preLines}: '${line}'`)
} else {
core.info(`...skipped line: '${line}'`)
lines[repo.index] = line
core.info(`...updated line ${repo.index + preLines}: '${line}'`)
}
}
})

await core.group('Writing README...', () => {
fs.writeFileSync(outputFile, lines.join('\n'))
core.info(`Finished writing to ${outputFile}`)
await core.group('Writing file...', () => {
const contents = pre + lines.join('\n') + post
fs.writeFileSync(fileName, contents)
core.info(`Finished writing to '${fileName}'`)
})
} catch (error) {
core.setFailed(error.message)
}
})()
}

function extractRepositories(lines) {
function parseFile (fileName) {
const file = fs.readFileSync(fileName, 'utf8')
const [preSection, bottomSection] = file.split('### Solutions')
const [repoSection, postSection] = bottomSection.split('### Live Streams')
const pre = preSection + '### Solutions'
const lines = repoSection.split('\n')
const post = '### Live Streams' + postSection
return { pre, lines, post }
}

function parseGithubRepos (lines) {
const repos = []

let collect = false
lines.some((line, index) => {
if (line === '### Solutions') {
collect = true
} else if (line === '### Live Streams') {
collect = false
} else if (collect) {
const idx1 = line.indexOf('[')
const idx2 = line.indexOf(']')
if (idx1 >= 0 && idx2 >= 0) {
repos.push({
index,
repoStr: line.slice(idx1 + 1, idx2)
})
if (line.indexOf('[') !== -1 && line.indexOf(']') !== -1) {
if (otherDomain(line)) {
core.info(`...line refers to non-GH domain: '${line}'`)
} else {
const idx1 = line.indexOf('[')
const idx2 = line.indexOf(']')

if (idx1 >= 0 && idx2 >= 0) {
const repoStr = line.slice(idx1 + 1, idx2)
const [owner, name] = repoStr.split('/')
repos.push({ index, owner, name })
core.info(`...parsed repo: '${owner}/${name}'`)
}
}
}

Expand All @@ -66,31 +91,19 @@ function extractRepositories(lines) {
return repos
}

async function generateLine(repoStr) {
const badge = await generateBadge(repoStr)
return `* [${repoStr}](https://github.com/${repoStr}) ![Last Commit on GitHub](${badge})`
function otherDomain (line) {
return (
!(
line.indexOf('[') < line.indexOf('/') &&
line.indexOf(']') > line.indexOf('/')
) ||
line.indexOf('gitlab.com') !== -1 ||
line.indexOf('gist.github.com') !== -1
)
}

async function generateBadge(repoStr) {
const [owner, repo] = repoStr.split('/')
const { label, message, color } = await endpoint(octokit, { owner, repo })

core.info(`...fetched repo ${repoStr}`)

return 'https://img.shields.io/badge/' + [label, message, color]
.map(s => encodeURIComponent(s.replace(/\-/g, '--')))
.join('-')
}

function shouldUpdate(oldLine, newLine) {
const lastReg = /Last Commit on GitHub/;
const badDateReg = /red\)$/;
return !lastReg.test(oldLine) ||
!badDateReg.test(newLine) ||
badDateReg.test(oldLine);
}

function otherDomain(line) {
return line.indexOf("gitlab.com") !== -1 ||
line.indexOf("gist.github.com") !== -1;
function generateLine (repo) {
const badge = generateBadge(repo)
const repoStr = `${repo.owner}/${repo.name}`
return `* [${repoStr}](https://github.com/${repoStr}) ![Last Commit on GitHub](${badge})`
}
Loading

0 comments on commit 44328ad

Please sign in to comment.