@shiinasaku/github-card 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -98
- package/dist/card.d.ts +2 -15
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +3951 -380
- package/dist/types.d.ts +8 -3
- package/dist/utils/font.d.ts +3 -1
- package/dist/utils/format.d.ts +7 -0
- package/dist/utils/icons.d.ts +3 -1
- package/dist/utils/index.d.ts +5 -6
- package/dist/utils/languages.d.ts +0 -1
- package/package.json +16 -19
- package/src/card.ts +392 -467
- package/src/gallery.ts +37 -0
- package/src/github.ts +180 -78
- package/src/index.ts +91 -167
- package/src/lib.ts +1 -1
- package/src/types.ts +8 -3
- package/src/utils/font.ts +4 -2
- package/src/utils/format.ts +62 -22
- package/src/utils/github-client.ts +43 -133
- package/src/utils/icons.ts +31 -10
- package/src/utils/index.ts +5 -6
- package/src/utils/languages.ts +1 -1
- package/fonts/Roboto-subset.woff2 +0 -0
- package/src/env.d.ts +0 -4
- package/src/input.css +0 -1
- package/src/tailwind.css +0 -3837
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
export class GitHubError extends Error {
|
|
2
|
-
constructor(
|
|
3
|
-
message: string,
|
|
4
|
-
public httpStatus?: number,
|
|
5
|
-
public errorCode?: string,
|
|
6
|
-
) {
|
|
2
|
+
constructor(message: string) {
|
|
7
3
|
super(message);
|
|
8
4
|
this.name = "GitHubError";
|
|
9
5
|
}
|
|
@@ -11,93 +7,48 @@ export class GitHubError extends Error {
|
|
|
11
7
|
|
|
12
8
|
export class NotFoundError extends GitHubError {
|
|
13
9
|
constructor(message = "Resource not found") {
|
|
14
|
-
super(message
|
|
10
|
+
super(message);
|
|
15
11
|
this.name = "NotFoundError";
|
|
16
12
|
}
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
export class RateLimitError extends GitHubError {
|
|
20
|
-
constructor(
|
|
21
|
-
message
|
|
22
|
-
public resetAt?: Date,
|
|
23
|
-
) {
|
|
24
|
-
super(message, 429, "RATE_LIMITED");
|
|
16
|
+
constructor(message = "GitHub API rate limit exceeded") {
|
|
17
|
+
super(message);
|
|
25
18
|
this.name = "RateLimitError";
|
|
26
19
|
}
|
|
27
20
|
}
|
|
28
21
|
|
|
29
22
|
export class AuthError extends GitHubError {
|
|
30
23
|
constructor(message = "GitHub authentication failed") {
|
|
31
|
-
super(message
|
|
24
|
+
super(message);
|
|
32
25
|
this.name = "AuthError";
|
|
33
26
|
}
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
constructor(
|
|
38
|
-
super(
|
|
29
|
+
class TimeoutError extends GitHubError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super("GitHub API request timed out");
|
|
39
32
|
this.name = "TimeoutError";
|
|
40
33
|
}
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
export interface GitHubClientOptions {
|
|
44
|
-
token?: string;
|
|
45
|
-
userAgent?: string;
|
|
46
|
-
timeoutMs?: number;
|
|
47
|
-
baseUrl?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface RequestOptions extends Omit<RequestInit, "body"> {
|
|
51
|
-
variables?: Record<string, unknown>;
|
|
52
|
-
timeoutMs?: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* A next-generation, generic GitHub GraphQL client.
|
|
57
|
-
* Designed to be robust, strongly typed, and publishable.
|
|
58
|
-
*/
|
|
59
36
|
export class GitHubClient {
|
|
60
|
-
private readonly token: string;
|
|
61
|
-
private readonly userAgent: string;
|
|
62
|
-
private readonly timeoutMs: number;
|
|
63
|
-
private readonly baseUrl: string;
|
|
64
|
-
|
|
65
|
-
constructor(options: GitHubClientOptions = {}) {
|
|
66
|
-
const token =
|
|
67
|
-
options.token || (typeof process !== "undefined" ? process.env?.GITHUB_TOKEN : "") || "";
|
|
68
|
-
if (!token) {
|
|
69
|
-
console.warn(
|
|
70
|
-
"GitHubClient initialized without a token. Requests requiring authentication will fail.",
|
|
71
|
-
);
|
|
72
|
-
}
|
|
37
|
+
constructor(private readonly options: { token: string; userAgent: string }) {}
|
|
73
38
|
|
|
74
|
-
|
|
75
|
-
this.userAgent = options.userAgent || "nextgen-github-client/1.0";
|
|
76
|
-
this.timeoutMs = options.timeoutMs || 10000;
|
|
77
|
-
this.baseUrl = options.baseUrl || "https://api.github.com/graphql";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Classifies fetch or API errors into distinct custom error classes.
|
|
82
|
-
*/
|
|
83
|
-
private classifyError(message: string, status: number, headers?: Headers): never {
|
|
39
|
+
private classifyError(message: string, status: number): never {
|
|
84
40
|
const lower = message.toLowerCase();
|
|
85
|
-
|
|
86
41
|
if (status === 404 || lower.includes("not resolve to a user") || lower.includes("not found")) {
|
|
87
42
|
throw new NotFoundError(message);
|
|
88
43
|
}
|
|
89
|
-
|
|
90
44
|
if (
|
|
91
45
|
status === 429 ||
|
|
92
46
|
status === 403 ||
|
|
93
47
|
lower.includes("rate limit") ||
|
|
94
48
|
lower.includes("abuse")
|
|
95
49
|
) {
|
|
96
|
-
|
|
97
|
-
const resetAt = resetHeader ? new Date(parseInt(resetHeader, 10) * 1000) : undefined;
|
|
98
|
-
throw new RateLimitError(message, resetAt);
|
|
50
|
+
throw new RateLimitError(message);
|
|
99
51
|
}
|
|
100
|
-
|
|
101
52
|
if (
|
|
102
53
|
status === 401 ||
|
|
103
54
|
lower.includes("bad credentials") ||
|
|
@@ -105,89 +56,48 @@ export class GitHubClient {
|
|
|
105
56
|
) {
|
|
106
57
|
throw new AuthError(message);
|
|
107
58
|
}
|
|
108
|
-
|
|
109
|
-
throw new GitHubError(message || `GitHub API error (${status})`, status);
|
|
59
|
+
throw new GitHubError(message || `GitHub API error (${status})`);
|
|
110
60
|
}
|
|
111
61
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* @param options Request options including variables and fetch options.
|
|
117
|
-
* @returns Strongly typed response data.
|
|
118
|
-
*/
|
|
119
|
-
public async request<T = any>(query: string, options: RequestOptions = {}): Promise<T> {
|
|
120
|
-
const { variables, timeoutMs = this.timeoutMs, signal: userSignal, ...fetchOptions } = options;
|
|
121
|
-
|
|
122
|
-
const headers = new Headers(fetchOptions.headers);
|
|
123
|
-
if (this.token && !headers.has("Authorization")) {
|
|
124
|
-
headers.set("Authorization", `bearer ${this.token}`);
|
|
125
|
-
}
|
|
126
|
-
if (!headers.has("Content-Type")) {
|
|
127
|
-
headers.set("Content-Type", "application/json");
|
|
128
|
-
}
|
|
129
|
-
if (!headers.has("User-Agent")) {
|
|
130
|
-
headers.set("User-Agent", this.userAgent);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const abortController = new AbortController();
|
|
134
|
-
const timeoutId =
|
|
135
|
-
timeoutMs > 0
|
|
136
|
-
? setTimeout(() => abortController.abort(new TimeoutError()), timeoutMs)
|
|
137
|
-
: undefined;
|
|
138
|
-
|
|
139
|
-
// Link user-provided signal if present
|
|
140
|
-
const onAbort = userSignal ? () => abortController.abort(userSignal.reason) : undefined;
|
|
141
|
-
if (userSignal) {
|
|
142
|
-
userSignal.addEventListener("abort", onAbort!);
|
|
143
|
-
if (userSignal.aborted) abortController.abort(userSignal.reason);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let response: Response;
|
|
147
|
-
|
|
62
|
+
async request<T>(
|
|
63
|
+
query: string,
|
|
64
|
+
{ variables, timeoutMs }: { variables: Record<string, unknown>; timeoutMs: number },
|
|
65
|
+
): Promise<T> {
|
|
148
66
|
try {
|
|
149
|
-
response = await fetch(
|
|
150
|
-
...fetchOptions,
|
|
67
|
+
const response = await fetch("https://api.github.com/graphql", {
|
|
151
68
|
method: "POST",
|
|
152
|
-
headers
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `bearer ${this.options.token}`,
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
"User-Agent": this.options.userAgent,
|
|
73
|
+
},
|
|
153
74
|
body: JSON.stringify({ query, variables }),
|
|
154
|
-
signal:
|
|
75
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
155
76
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
throw new GitHubError(`Network error: ${error.message || "Unknown error"}`);
|
|
161
|
-
} finally {
|
|
162
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
163
|
-
if (userSignal) {
|
|
164
|
-
userSignal.removeEventListener("abort", onAbort!);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
const message = await response.text();
|
|
79
|
+
this.classifyError(message || response.statusText, response.status);
|
|
165
80
|
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!response.ok) {
|
|
169
|
-
const errorText = await response.text().catch(() => "");
|
|
170
|
-
this.classifyError(errorText || response.statusText, response.status, response.headers);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const body = (await response.json().catch(() => ({}))) as any;
|
|
174
81
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
82
|
+
const body = (await response.json()) as { data?: T; errors?: { message: string }[] };
|
|
83
|
+
if (body.errors?.length) {
|
|
84
|
+
this.classifyError(body.errors[0]?.message || "GraphQL error", response.status);
|
|
85
|
+
}
|
|
86
|
+
if (body.data == null) {
|
|
87
|
+
throw new GitHubError("Invalid response: No data returned from GraphQL endpoint");
|
|
88
|
+
}
|
|
89
|
+
return body.data;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof GitHubError) throw error;
|
|
92
|
+
if (
|
|
93
|
+
error instanceof Error &&
|
|
94
|
+
(error.name === "AbortError" || error.name === "TimeoutError")
|
|
95
|
+
) {
|
|
96
|
+
throw new TimeoutError();
|
|
97
|
+
}
|
|
182
98
|
throw new GitHubError(
|
|
183
|
-
|
|
184
|
-
response.status,
|
|
99
|
+
`Network error: ${error instanceof Error ? error.message : String(error)}`,
|
|
185
100
|
);
|
|
186
101
|
}
|
|
187
|
-
|
|
188
|
-
return body.data as T;
|
|
189
102
|
}
|
|
190
103
|
}
|
|
191
|
-
|
|
192
|
-
// Export a default singleton instance for convenience
|
|
193
|
-
export const githubClient = new GitHubClient();
|
package/src/utils/icons.ts
CHANGED
|
@@ -1,18 +1,39 @@
|
|
|
1
|
+
import { escapeXml } from "./format";
|
|
2
|
+
|
|
1
3
|
export const icons = {
|
|
2
|
-
star: `<path
|
|
4
|
+
star: `<path d="M10.92 2.868a1.25 1.25 0 0 1 2.16 0l2.795 4.798l5.428 1.176a1.25 1.25 0 0 1 .667 2.054l-3.7 4.141l.56 5.525a1.25 1.25 0 0 1-1.748 1.27L12 19.592l-5.082 2.24a1.25 1.25 0 0 1-1.748-1.27l.56-5.525l-3.7-4.14a1.25 1.25 0 0 1 .667-2.055l5.428-1.176z"/>`,
|
|
3
5
|
commit: `<path fill-rule="evenodd" d="M10.5 7.75a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm1.43.75a4.002 4.002 0 01-7.86 0H.75a.75.75 0 110-1.5h3.32a4.001 4.001 0 017.86 0h3.32a.75.75 0 110 1.5h-3.32z"/>`,
|
|
4
|
-
pr: `<path
|
|
5
|
-
issue: `<path
|
|
6
|
-
repo: `<path
|
|
6
|
+
pr: `<path d="M4 18a2 2 0 1 0 4 0a2 2 0 1 0-4 0M4 6a2 2 0 1 0 4 0a2 2 0 1 0-4 0m12 12a2 2 0 1 0 4 0a2 2 0 1 0-4 0M6 8v8"/><path d="M11 6h5a2 2 0 0 1 2 2v8"/><path d="m14 9l-3-3l3-3"/>`,
|
|
7
|
+
issue: `<path d="M15.314 2a2 2 0 0 1 1.414.586l4.686 4.686A2 2 0 0 1 22 8.686v6.628a2 2 0 0 1-.586 1.414l-4.686 4.686a2 2 0 0 1-1.414.586H8.686a2 2 0 0 1-1.414-.586l-4.686-4.686A2 2 0 0 1 2 15.314V8.686a2 2 0 0 1 .586-1.414l4.686-4.686A2 2 0 0 1 8.686 2zm0 2H8.686L4 8.686v6.628L8.686 20h6.628L20 15.314V8.686zM12 15a1 1 0 1 1 0 2a1 1 0 0 1 0-2m0-9a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V7a1 1 0 0 1 1-1"/>`,
|
|
8
|
+
repo: `<path d="M6 19.623c-.69-.195-1.232-.491-1.682-.941C3 17.364 3 15.242 3 11s0-6.364 1.318-7.682S7.758 2 12 2s6.364 0 7.682 1.318S21 6.758 21 11s0 6.364-1.318 7.682c-.45.45-.993.746-1.682.941"/><path d="M12 20.193c-.414 0-.748.32-1.414.958c-.647.619-.97.929-1.242.831a.5.5 0 0 1-.096-.046C9 21.779 9 21.31 9 20.376v-3.125c0-1.532 0-2.299.44-2.775C9.878 14 10.585 14 12 14s2.121 0 2.56.476s.44 1.243.44 2.775v3.125c0 .935 0 1.403-.248 1.56a.5.5 0 0 1-.096.046c-.272.098-.595-.212-1.242-.831c-.666-.639-1-.958-1.414-.958M8 10h8M8 6h4"/>`,
|
|
7
9
|
x: `<path d="M4 4l11.733 16h4.267l-11.733 -16l-4.267 0" /><path d="M4 20l6.768 -6.768m2.46 -2.46l6.772 -6.772" />`,
|
|
8
10
|
};
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
type IconName = keyof typeof icons;
|
|
13
|
+
|
|
14
|
+
const iconMeta: Record<
|
|
15
|
+
IconName,
|
|
16
|
+
{ viewBox: string; kind: "fill" | "stroke"; strokeWidth?: string }
|
|
17
|
+
> = {
|
|
18
|
+
star: { viewBox: "0 0 24 24", kind: "fill" },
|
|
19
|
+
commit: { viewBox: "0 0 16 16", kind: "fill" },
|
|
20
|
+
pr: { viewBox: "0 0 24 24", kind: "stroke", strokeWidth: "2" },
|
|
21
|
+
issue: { viewBox: "0 0 24 24", kind: "fill" },
|
|
22
|
+
repo: { viewBox: "0 0 24 24", kind: "stroke", strokeWidth: "1.5" },
|
|
23
|
+
x: { viewBox: "0 0 24 24", kind: "stroke", strokeWidth: "2" },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function icon(name: IconName, color: string, size = 16): string {
|
|
11
27
|
const path = icons[name];
|
|
12
28
|
if (!path) return "";
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
const meta = iconMeta[name];
|
|
30
|
+
const viewBox = meta?.viewBox ?? "0 0 16 16";
|
|
31
|
+
const colorStr = escapeXml(
|
|
32
|
+
/^(?:[\da-f]{3}|[\da-f]{4}|[\da-f]{6}|[\da-f]{8})$/i.test(color) ? `#${color}` : color,
|
|
33
|
+
);
|
|
34
|
+
const attrs =
|
|
35
|
+
meta?.kind === "stroke"
|
|
36
|
+
? `fill="none" stroke="${colorStr}" stroke-width="${meta.strokeWidth ?? "1.8"}" stroke-linecap="round" stroke-linejoin="round"`
|
|
37
|
+
: `fill="${colorStr}"`;
|
|
38
|
+
return `<svg width="${size}" height="${size}" viewBox="${viewBox}" ${attrs} role="img">${path}</svg>`;
|
|
18
39
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export { resolveColors, themes
|
|
2
|
-
export { kFormat, escapeXml, wrapText } from "./format";
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export * from "./colors";
|
|
1
|
+
export { resolveColors, themes } from "./themes";
|
|
2
|
+
export { kFormat, escapeXml, wrapText, measureText } from "./format";
|
|
3
|
+
export { icon } from "./icons";
|
|
4
|
+
export { FONT_FAMILY } from "./font";
|
|
5
|
+
export { resolveTw } from "./colors";
|
package/src/utils/languages.ts
CHANGED
|
Binary file
|
package/src/env.d.ts
DELETED
package/src/input.css
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@import "tailwindcss";
|