kopia lustrzana https://github.com/badgen/badgen.net
badge: add winget support (#392)
* badge: add winget support * refactor: introduce github rest/graphql client libpull/397/head
rodzic
d155bd63a6
commit
55926c5fb3
|
|
@ -2,8 +2,9 @@ import cheerio from 'cheerio'
|
|||
import distanceToNow from 'date-fns/formatDistanceToNow'
|
||||
|
||||
import got from '../libs/got'
|
||||
import { restGithub, queryGithub } from '../libs/github'
|
||||
import { createBadgenHandler, PathArgs } from '../libs/create-badgen-handler'
|
||||
import { version, millify, coverageColor } from '../libs/utils'
|
||||
import { createBadgenHandler, BadgenError, PathArgs } from '../libs/create-badgen-handler'
|
||||
|
||||
export default createBadgenHandler({
|
||||
title: 'GitHub',
|
||||
|
|
@ -68,37 +69,6 @@ export default createBadgenHandler({
|
|||
}
|
||||
})
|
||||
|
||||
const pickGithubToken = () => {
|
||||
const { GH_TOKENS } = process.env
|
||||
if (!GH_TOKENS) {
|
||||
throw new BadgenError({
|
||||
status: 'token required'
|
||||
})
|
||||
}
|
||||
|
||||
const tokens = GH_TOKENS.split(',')
|
||||
return tokens[Math.floor(Math.random() * tokens.length)]
|
||||
}
|
||||
|
||||
// request github api v3 (rest)
|
||||
const restGithub = (path, preview = 'hellcat') => got.get(`https://api.github.com/${path}`, {
|
||||
headers: {
|
||||
Authorization: `token ${pickGithubToken()}`,
|
||||
Accept: `application/vnd.github.${preview}-preview+json`
|
||||
}
|
||||
}).json<any>()
|
||||
|
||||
// request github api v4 (graphql)
|
||||
const queryGithub = query => {
|
||||
return got.post('https://api.github.com/graphql', {
|
||||
json: { query },
|
||||
headers: {
|
||||
Authorization: `token ${pickGithubToken()}`,
|
||||
Accept: 'application/vnd.github.hawkgirl-preview+json'
|
||||
}
|
||||
}).json<any>()
|
||||
}
|
||||
|
||||
// https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||
const statesColor = {
|
||||
pending: 'orange',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
import got from '../libs/got'
|
||||
import { restGithub } from '../libs/github'
|
||||
import { parseDocument } from 'yaml'
|
||||
import { basename, extname } from 'path'
|
||||
import { version, versionColor } from '../libs/utils'
|
||||
import { createBadgenHandler, PathArgs } from '../libs/create-badgen-handler'
|
||||
|
||||
const last = <T>(arr: T[]): T => arr[arr.length - 1]
|
||||
|
||||
interface Part {
|
||||
number: number
|
||||
other: string
|
||||
}
|
||||
|
||||
class Version {
|
||||
_source: string
|
||||
_parts: Part[]
|
||||
|
||||
constructor (input: string) {
|
||||
this._source = input
|
||||
|
||||
const parts = input.split('.').map(segment => {
|
||||
return new Version.Part(segment)
|
||||
})
|
||||
|
||||
while (parts.length) {
|
||||
const part = last(parts)
|
||||
if (part.number || part.other) break
|
||||
parts.pop()
|
||||
}
|
||||
|
||||
this._parts = parts
|
||||
}
|
||||
|
||||
get parts() {
|
||||
return this._parts
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._source
|
||||
}
|
||||
|
||||
static comparator(versionA: Version, versionB: Version): number {
|
||||
let i = 0
|
||||
while (i < versionA.parts.length) {
|
||||
if (i >= versionB.parts.length) break
|
||||
|
||||
const partA = versionA.parts[i]
|
||||
const partB = versionB.parts[i]
|
||||
const result = Version.Part.comparator(partA, partB)
|
||||
if (result) return result
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
if (versionA.parts.length < versionB.parts.length) return -1
|
||||
if (versionA.parts.length > versionB.parts.length) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
private static Part = class implements Part {
|
||||
_source: string
|
||||
_number: number
|
||||
_other: string
|
||||
|
||||
constructor(input: string) {
|
||||
this._source = input
|
||||
|
||||
const [num, rest] = input.split(/([^\d]+)/)
|
||||
this._number = parseInt(num, 10) || 0
|
||||
this._other = rest
|
||||
}
|
||||
|
||||
get number() {
|
||||
return this._number
|
||||
}
|
||||
|
||||
get other() {
|
||||
return this._other
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._source
|
||||
}
|
||||
|
||||
static comparator(partA: Part, partB: Part): number {
|
||||
if (partA.number < partB.number) return -1
|
||||
if (partA.number > partB.number) return 1
|
||||
if (partA.other < partB.other) return -1
|
||||
if (partA.other > partB.other) return 1
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createBadgenHandler({
|
||||
title: 'winget',
|
||||
examples: {
|
||||
'/winget/v/GitHub.cli': 'version',
|
||||
'/winget/v/Balena.Etcher': 'version',
|
||||
'/winget/license/Arduino.Arduino': 'license'
|
||||
},
|
||||
handlers: {
|
||||
'/winget/:topic<v|license>/:appId': handler
|
||||
}
|
||||
})
|
||||
|
||||
async function handler ({ topic, appId }: PathArgs) {
|
||||
switch (topic) {
|
||||
case 'v': {
|
||||
const versions = await fetchVersions(appId)
|
||||
const ver = last(versions).toString()
|
||||
|
||||
return {
|
||||
subject: 'winget',
|
||||
status: version(ver),
|
||||
color: versionColor(ver)
|
||||
}
|
||||
}
|
||||
case 'license': {
|
||||
const yaml = await fetchManifest(appId)
|
||||
const manifest = parseDocument(yaml)
|
||||
const license = manifest.get('License')
|
||||
|
||||
return {
|
||||
subject: 'license',
|
||||
status: license || 'unknown',
|
||||
color: 'blue'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchManifest(appId: string) {
|
||||
const versions = await fetchVersions(appId)
|
||||
const version = last(versions)
|
||||
const path = [...appId.split('.'), `${version}.yaml`].join('/')
|
||||
return got(`https://github.com/microsoft/winget-pkgs/raw/master/manifests/${path}`).text()
|
||||
}
|
||||
|
||||
async function fetchVersions(appId: string): Promise<Version[]> {
|
||||
const path = appId.replace(/\./, '/')
|
||||
const files = await restGithub<any[]>(`repos/microsoft/winget-pkgs/contents/manifests/${path}`)
|
||||
const versions = files.map(file => {
|
||||
const name = basename(file.name, extname(file.name))
|
||||
return new Version(name)
|
||||
})
|
||||
versions.sort(Version.comparator)
|
||||
return versions
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ export const liveBadgeList = [
|
|||
'haxelib',
|
||||
'opam',
|
||||
'scoop',
|
||||
'winget',
|
||||
'f-droid',
|
||||
'pub',
|
||||
// CI
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
import got from './got'
|
||||
import { BadgenError } from './create-badgen-handler'
|
||||
|
||||
const rand = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)]
|
||||
|
||||
// request github api v3 (rest)
|
||||
export function restGithub<T = any>(path: string, preview = 'hellcat') {
|
||||
const headers = {
|
||||
authorization: `token ${pickGithubToken()}`,
|
||||
accept: `application/vnd.github.${preview}-preview+json`
|
||||
}
|
||||
const prefixUrl = 'https://api.github.com/'
|
||||
return got.get(path, { prefixUrl, headers }).json<T>()
|
||||
}
|
||||
|
||||
// request github api v4 (graphql)
|
||||
export function queryGithub<T = any>(query) {
|
||||
const headers = {
|
||||
authorization: `token ${pickGithubToken()}`,
|
||||
accept: 'application/vnd.github.hawkgirl-preview+json'
|
||||
}
|
||||
const json = { query }
|
||||
return got.post('https://api.github.com/graphql', { json, headers }).json<T>()
|
||||
}
|
||||
|
||||
function pickGithubToken() {
|
||||
const { GH_TOKENS } = process.env
|
||||
if (!GH_TOKENS) {
|
||||
throw new BadgenError({ status: 'token required' })
|
||||
}
|
||||
const tokens = GH_TOKENS.split(',').map(segment => segment.trim())
|
||||
return rand(tokens)
|
||||
}
|
||||
Ładowanie…
Reference in New Issue