@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.
@@ -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, 404, "NOT_FOUND");
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 = "GitHub API rate limit exceeded",
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, 401, "UNAUTHENTICATED");
24
+ super(message);
32
25
  this.name = "AuthError";
33
26
  }
34
27
  }
35
28
 
36
- export class TimeoutError extends GitHubError {
37
- constructor(message = "GitHub API request timed out") {
38
- super(message, 408, "TIMEOUT");
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
- this.token = token;
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
- const resetHeader = headers?.get("x-ratelimit-reset");
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
- * Executes a GraphQL query against the GitHub API.
114
- *
115
- * @param query The GraphQL query string.
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(this.baseUrl, {
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: abortController.signal,
75
+ signal: AbortSignal.timeout(timeoutMs),
155
76
  });
156
- } catch (error: any) {
157
- if (error.name === "AbortError" || error instanceof TimeoutError) {
158
- throw new TimeoutError(error.message || "Request timed out");
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
- if (body.errors && body.errors.length > 0) {
176
- // Throw the first error specifically, but could be enhanced to aggregate
177
- const firstError = body.errors[0];
178
- this.classifyError(firstError.message, response.status, response.headers);
179
- }
180
-
181
- if (!body.data) {
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
- "Invalid response: No data returned from GraphQL endpoint",
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();
@@ -1,18 +1,39 @@
1
+ import { escapeXml } from "./format";
2
+
1
3
  export const icons = {
2
- star: `<path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/>`,
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 fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/>`,
5
- issue: `<path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/>`,
6
- repo: `<path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></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
- export function icon(name: keyof typeof icons, color: string, size = 16): string {
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 viewBox = name === "x" ? "0 0 24 24" : "0 0 16 16";
14
- const colorStr = color.startsWith("#") ? color : `#${color}`;
15
- const fill =
16
- name === "x" ? `stroke="${colorStr}" stroke-width="2" fill="none"` : `fill="${colorStr}"`;
17
- return `<svg width="${size}" height="${size}" viewBox="${viewBox}" ${fill} role="img">${path}</svg>`;
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
  }
@@ -1,6 +1,5 @@
1
- export { resolveColors, themes, type Theme } from "./themes";
2
- export { kFormat, escapeXml, wrapText } from "./format";
3
- export { getLangColor } from "./languages";
4
- export { icons, icon } from "./icons";
5
- export { FONT_FACE, FONT_FAMILY } from "./font";
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";
@@ -1,4 +1,4 @@
1
- export const langColors: Record<string, string> = {
1
+ const langColors: Record<string, string> = {
2
2
  "1C Enterprise": "#814CCC",
3
3
  "2-Dimensional Array": "#38761D",
4
4
  "4D": "#004289",
Binary file
package/src/env.d.ts DELETED
@@ -1,4 +0,0 @@
1
- declare module "*.css" {
2
- const content: string;
3
- export default content;
4
- }
package/src/input.css DELETED
@@ -1 +0,0 @@
1
- @import "tailwindcss";