mirror of
https://github.com/tiennm99/awesome-advent-of-code.git
synced 2026-10-11 03:13:00 +00:00
update-badge: rewrite to use GraphQL and process multiple files
This commit is contained in:
1 parent
e2a3ca41d8
commit
0db8c385f6
5 files changed
+169
-114
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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'
|
||||
+31
-43
@@ -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' }
|
||||
}
|
||||
|
||||
const message = moment(lastCommitDayStr).format('YYYY-MM-DD')
|
||||
return {
|
||||
message,
|
||||
color: age(message),
|
||||
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) })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = async function invokeHandler(octokit, query) {
|
||||
return Object.assign({}, badgeDefaults, await handle(octokit, query))
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.')
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,62 +1,79 @@
|
||||
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 lines = await core.group(`Reading from '${fileName}'...`, () =>
|
||||
fs.readFileSync(fileName, 'utf8').split('\n')
|
||||
)
|
||||
|
||||
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}`)
|
||||
|
||||
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 + 1}: '${line}'`)
|
||||
} else {
|
||||
core.info(`...skipped line: '${line}'`)
|
||||
lines[repo.index] = line
|
||||
core.info(`...updated line ${repo.index + 1}: '${line}'`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await core.group('Writing README...', () => {
|
||||
fs.writeFileSync(outputFile, lines.join('\n'))
|
||||
core.info(`Finished writing to ${outputFile}`)
|
||||
await core.group('Writing file...', () => {
|
||||
fs.writeFileSync(fileName, lines.join('\n'))
|
||||
core.info(`Finished writing to '${fileName}'`)
|
||||
})
|
||||
} catch (error) {
|
||||
core.setFailed(error.message)
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
function extractRepositories(lines) {
|
||||
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 (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}'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,31 +83,14 @@ function extractRepositories(lines) {
|
||||
return repos
|
||||
}
|
||||
|
||||
async function generateLine(repoStr) {
|
||||
const badge = await generateBadge(repoStr)
|
||||
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}) `
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
const core = require('@actions/core')
|
||||
|
||||
// limit size of each request to avoid getting timed out
|
||||
const chunkSize = 200
|
||||
|
||||
module.exports = async function updateRepos (octokit, repos) {
|
||||
let responses = []
|
||||
|
||||
for (let i = 0; i < repos.length; i += chunkSize) {
|
||||
const chunk = repos.slice(i, i + chunkSize)
|
||||
responses.push(await queryRepos(octokit, chunk))
|
||||
}
|
||||
|
||||
return responses.flatMap(response => {
|
||||
return Object.entries(response).map(([key, obj]) => {
|
||||
const index = parseInt(key.replace('index', ''))
|
||||
|
||||
if (obj === null) {
|
||||
return repos.find(repo => repo.index === index)
|
||||
} else {
|
||||
return {
|
||||
index,
|
||||
owner: obj.owner.login,
|
||||
name: obj.name,
|
||||
lastCommit: obj.defaultBranchRef.target.committedDate
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function queryRepos (octokit, repos) {
|
||||
const query = buildQuery(repos)
|
||||
|
||||
try {
|
||||
return await octokit.graphql(query)
|
||||
} catch (error) {
|
||||
if (error.errors !== undefined) {
|
||||
const softFail = error.errors.every(
|
||||
e => e.message.indexOf('Could not resolve to a Repository') !== -1
|
||||
)
|
||||
|
||||
if (softFail) {
|
||||
core.info(error)
|
||||
return error.data
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
function buildQuery (repos) {
|
||||
const frag = `fragment props on Repository {
|
||||
owner {
|
||||
login
|
||||
}
|
||||
name
|
||||
defaultBranchRef {
|
||||
target {
|
||||
... on Commit {
|
||||
committedDate
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
const query = repos
|
||||
.map(repo => {
|
||||
return `index${repo.index}: repository(owner: "${repo.owner}", name: "${repo.name}") { ...props }`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
return `${frag}\n\n{\n${query}\n}`
|
||||
}
|
||||
Reference in new issue
Block a user