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
	
	 Dario Vladović
						Dario Vladović