1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 |
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x2
x7
x7
x2
x3
x1
x1
x1
x2
x2
x3
x2
x3
x3
x3
x3
x3
x3
x3
x3
x3
x3
x2
x7
x7
x7
x7
x2
x4
x4
x4
x4
x4
x4
x2 |
|
import type { Arg } from "@libs/typing/types"
import type { RequestInterface } from "@octokit/types"
import { globToRegExp } from "@std/path/posix/glob-to-regexp"
import { Octokit } from "@octokit/rest"
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql"
import { paginateRest } from "@octokit/plugin-paginate-rest"
import { getLogger, type Logger } from "@logtape/logtape"
export type { Arg, Logger }
export type GithubOptions = {
logger?: string[]
fetch?: typeof globalThis.fetch
meta?: ImportMeta
}
export type GitHubConfiguration = {
token?: string
ua?: string
timezone?: string
url?: string
}
export class GitHub {
constructor({ logger: category = ["github"], fetch = globalThis.fetch, meta = import.meta }: GithubOptions = {}, { token, ua, timezone, url }: GitHubConfiguration = {}) {
this.#meta = meta
this.#log = getLogger(category)
this.#log.info(`octokit version: ${Octokit.VERSION}`)
this.#octokit = new (Octokit.plugin(paginateGraphQL, paginateRest))({
userAgent: ua,
auth: token,
timeZone: timezone,
baseUrl: url,
log: { error: this.#log.error.bind(this.#log), warn: this.#log.warn.bind(this.#log), info: this.#log.info.bind(this.#log), debug: this.#log.debug.bind(this.#log) },
request: { fetch },
})
}
readonly #meta
readonly #log
readonly #octokit
get api(): InstanceType<typeof Octokit>["rest"] {
return this.#octokit.rest
}
async entity(handle: string): Promise<{ entity: "user" | "organization"; login: string } | { entity: "repository"; owner: string; repo: string }> {
if (globToRegExp("*/*").test(handle)) {
const [owner, repo] = handle.split("/")
return { entity: "repository", owner, repo }
}
const { data: { type } } = await this.rest(this.api.users.getByUsername, { username: handle })
return { entity: type.toLowerCase() as "user" | "organization", login: handle }
}
async ratelimit(): Promise<{ core: number; graphql: number; search: number }> {
const { data: { resources: { core, search, graphql = { remaining: 0, limit: 0 } } } } = await this.rest(this.api.rateLimit.get)
this.#log.debug("current rate limit: {quota}", {
quota: {
core: `${core.remaining}/${core.limit}`,
graphql: `${graphql.remaining}/${graphql.limit}`,
search: `${search.remaining}/${search.limit}`,
},
})
return { core: core.remaining, graphql: graphql.remaining, search: search.remaining }
}
rest<T extends RequestInterface>(endpoint: T, vars?: Arg<T>, _?: { paginate?: false }): ReturnType<T>
rest<T extends RequestInterface>(endpoint: T, vars: Arg<T>, _: { paginate: true }): Promise<Awaited<ReturnType<T>>["data"]>
rest<T extends RequestInterface>(endpoint: T, vars = {} as Arg<T>, { paginate = false } = {}) {
const { endpoint: { DEFAULTS: { method, url } } } = endpoint
this.#log.info(`REST ${method} ${url}`)
return paginate ? this.#octokit.paginate(endpoint, vars) : endpoint(vars)
}
async graphql<T = any>(query: string, vars = {} as Record<PropertyKey, unknown>, { paginate = false } = {}): Promise<T> {
this.#log.info(`GraphQL ${query}`)
const path = this.#meta.resolve(`./.github/${query}.graphql`)
this.#log.trace(`loaded query: ${path}\n${query}`)
query = await fetch(path).then((response) => response.text())
return paginate ? this.#octokit.graphql.paginate(query, vars) as T : this.#octokit.graphql(query, vars)
}
}
|