@rmdes/indiekit-endpoint-github 1.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 +107 -0
- package/assets/icon.svg +4 -0
- package/assets/styles.css +245 -0
- package/includes/@indiekit-endpoint-github-commits.njk +13 -0
- package/includes/@indiekit-endpoint-github-stars.njk +13 -0
- package/includes/@indiekit-endpoint-github-widget.njk +12 -0
- package/index.js +98 -0
- package/lib/controllers/activity.js +124 -0
- package/lib/controllers/commits.js +84 -0
- package/lib/controllers/contributions.js +60 -0
- package/lib/controllers/dashboard.js +138 -0
- package/lib/controllers/featured.js +116 -0
- package/lib/controllers/stars.js +84 -0
- package/lib/github-client.js +168 -0
- package/lib/utils.js +198 -0
- package/locales/en.json +38 -0
- package/package.json +51 -0
- package/views/activity.njk +22 -0
- package/views/commits.njk +21 -0
- package/views/contributions.njk +24 -0
- package/views/github.njk +155 -0
- package/views/stars.njk +26 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { GitHubClient } from "../github-client.js";
|
|
2
|
+
import * as utils from "../utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Display PRs and issues created by user
|
|
6
|
+
* @type {import("express").RequestHandler}
|
|
7
|
+
*/
|
|
8
|
+
export const contributionsController = {
|
|
9
|
+
async get(request, response, next) {
|
|
10
|
+
try {
|
|
11
|
+
const { username, token, cacheTtl, limits } =
|
|
12
|
+
request.app.locals.application.githubConfig;
|
|
13
|
+
|
|
14
|
+
if (!username) {
|
|
15
|
+
return response.render("contributions", {
|
|
16
|
+
title: response.locals.__("github.contributions.title"),
|
|
17
|
+
error: { message: response.locals.__("github.error.noUsername") },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const client = new GitHubClient({ token, cacheTtl });
|
|
22
|
+
|
|
23
|
+
let events = [];
|
|
24
|
+
try {
|
|
25
|
+
events = await client.getUserEvents(username, 100);
|
|
26
|
+
} catch (apiError) {
|
|
27
|
+
console.error("GitHub API error:", apiError);
|
|
28
|
+
return response.render("contributions", {
|
|
29
|
+
title: response.locals.__("github.contributions.title"),
|
|
30
|
+
actions: [],
|
|
31
|
+
parent: {
|
|
32
|
+
href: request.baseUrl,
|
|
33
|
+
text: response.locals.__("github.title"),
|
|
34
|
+
},
|
|
35
|
+
error: {
|
|
36
|
+
message: apiError.message || "Failed to fetch contributions",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const contributions = utils
|
|
42
|
+
.extractContributions(events)
|
|
43
|
+
.slice(0, limits.contributions * 2);
|
|
44
|
+
|
|
45
|
+
response.render("contributions", {
|
|
46
|
+
title: response.locals.__("github.contributions.title"),
|
|
47
|
+
actions: [],
|
|
48
|
+
parent: {
|
|
49
|
+
href: request.baseUrl,
|
|
50
|
+
text: response.locals.__("github.title"),
|
|
51
|
+
},
|
|
52
|
+
contributions,
|
|
53
|
+
username,
|
|
54
|
+
mountPath: request.baseUrl,
|
|
55
|
+
});
|
|
56
|
+
} catch (error) {
|
|
57
|
+
next(error);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { GitHubClient } from "../github-client.js";
|
|
2
|
+
import * as utils from "../utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Display GitHub activity dashboard
|
|
6
|
+
* @type {import("express").RequestHandler}
|
|
7
|
+
*/
|
|
8
|
+
export const dashboardController = {
|
|
9
|
+
async get(request, response, next) {
|
|
10
|
+
try {
|
|
11
|
+
console.log("[GitHub Endpoint] Dashboard controller started");
|
|
12
|
+
|
|
13
|
+
const { githubConfig } = request.app.locals.application;
|
|
14
|
+
|
|
15
|
+
if (!githubConfig) {
|
|
16
|
+
console.error("[GitHub Endpoint] ERROR: githubConfig is undefined");
|
|
17
|
+
return response.status(500).render("github", {
|
|
18
|
+
title: "GitHub",
|
|
19
|
+
actions: [],
|
|
20
|
+
error: { message: "GitHub endpoint not configured correctly" },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { username, token, cacheTtl, limits, featuredRepos } = githubConfig;
|
|
25
|
+
console.log("[GitHub Endpoint] Config:", {
|
|
26
|
+
username,
|
|
27
|
+
hasToken: !!token,
|
|
28
|
+
cacheTtl,
|
|
29
|
+
limits,
|
|
30
|
+
featuredRepos: featuredRepos || [],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!username) {
|
|
34
|
+
console.log("[GitHub Endpoint] No username configured");
|
|
35
|
+
return response.render("github", {
|
|
36
|
+
title: response.locals.__("github.title"),
|
|
37
|
+
actions: [],
|
|
38
|
+
error: { message: response.locals.__("github.error.noUsername") },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const client = new GitHubClient({ token, cacheTtl });
|
|
43
|
+
console.log("[GitHub Endpoint] Using authenticated API:", !!token);
|
|
44
|
+
|
|
45
|
+
let user;
|
|
46
|
+
let events = [];
|
|
47
|
+
let starred = [];
|
|
48
|
+
let repos = [];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
console.log("[GitHub Endpoint] Fetching GitHub data for:", username);
|
|
52
|
+
[user, events, starred, repos] = await Promise.all([
|
|
53
|
+
client.getUser(username),
|
|
54
|
+
client.getUserEvents(username, 30),
|
|
55
|
+
client.getUserStarred(username, limits.stars),
|
|
56
|
+
client.getUserRepos(username, limits.repos || 10),
|
|
57
|
+
]);
|
|
58
|
+
console.log("[GitHub Endpoint] Raw user data:", JSON.stringify(user));
|
|
59
|
+
console.log("[GitHub Endpoint] Events count:", events?.length);
|
|
60
|
+
console.log("[GitHub Endpoint] Event types:", events?.map(e => e.type));
|
|
61
|
+
console.log("[GitHub Endpoint] Starred count:", starred?.length);
|
|
62
|
+
console.log("[GitHub Endpoint] Repos count:", repos?.length);
|
|
63
|
+
} catch (apiError) {
|
|
64
|
+
console.error("[GitHub Endpoint] API error:", apiError);
|
|
65
|
+
return response.render("github", {
|
|
66
|
+
title: response.locals.__("github.title"),
|
|
67
|
+
actions: [],
|
|
68
|
+
error: { message: apiError.message || "Failed to fetch GitHub data" },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log("[GitHub Endpoint] Processing data...");
|
|
73
|
+
const commits = utils.extractCommits(events);
|
|
74
|
+
console.log("[GitHub Endpoint] Extracted commits:", commits?.length);
|
|
75
|
+
|
|
76
|
+
const contributions = utils.extractContributions(events);
|
|
77
|
+
console.log("[GitHub Endpoint] Extracted contributions:", contributions?.length);
|
|
78
|
+
|
|
79
|
+
const stars = utils.formatStarred(starred);
|
|
80
|
+
console.log("[GitHub Endpoint] Formatted stars:", stars?.length);
|
|
81
|
+
|
|
82
|
+
const repositories = utils.formatRepos(repos);
|
|
83
|
+
console.log("[GitHub Endpoint] Formatted repos:", repositories?.length);
|
|
84
|
+
|
|
85
|
+
// Fetch commits from featured repos
|
|
86
|
+
let featured = [];
|
|
87
|
+
if (featuredRepos && featuredRepos.length > 0) {
|
|
88
|
+
console.log("[GitHub Endpoint] Fetching featured repos:", featuredRepos);
|
|
89
|
+
const featuredPromises = featuredRepos.map(async (repoPath) => {
|
|
90
|
+
const [owner, repo] = repoPath.split("/");
|
|
91
|
+
try {
|
|
92
|
+
const [repoData, repoCommits] = await Promise.all([
|
|
93
|
+
client.getRepo(owner, repo),
|
|
94
|
+
client.getRepoCommits(owner, repo, 5),
|
|
95
|
+
]);
|
|
96
|
+
return {
|
|
97
|
+
...utils.formatRepos([repoData])[0],
|
|
98
|
+
commits: repoCommits.map((c) => ({
|
|
99
|
+
sha: c.sha.slice(0, 7),
|
|
100
|
+
message: utils.truncate(c.commit.message.split("\n")[0], 60),
|
|
101
|
+
url: c.html_url,
|
|
102
|
+
author: c.commit.author.name,
|
|
103
|
+
date: c.commit.author.date,
|
|
104
|
+
})),
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`[GitHub Endpoint] Error fetching ${repoPath}:`, error.message);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
featured = (await Promise.all(featuredPromises)).filter(Boolean);
|
|
112
|
+
console.log("[GitHub Endpoint] Featured repos loaded:", featured.length);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const starsLimit = limits.stars || 20;
|
|
116
|
+
const reposLimit = limits.repos || 10;
|
|
117
|
+
|
|
118
|
+
console.log("[GitHub Endpoint] Rendering with limits - stars:", starsLimit, "repos:", reposLimit);
|
|
119
|
+
|
|
120
|
+
response.render("github", {
|
|
121
|
+
title: response.locals.__("github.title"),
|
|
122
|
+
actions: [],
|
|
123
|
+
user,
|
|
124
|
+
commits: commits.slice(0, limits.commits || 10),
|
|
125
|
+
contributions: contributions.slice(0, limits.contributions || 5),
|
|
126
|
+
stars: stars.slice(0, starsLimit),
|
|
127
|
+
repositories: repositories.slice(0, reposLimit),
|
|
128
|
+
featured,
|
|
129
|
+
mountPath: request.baseUrl,
|
|
130
|
+
});
|
|
131
|
+
console.log("[GitHub Endpoint] Render complete");
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("[GitHub Endpoint] Unexpected error:", error);
|
|
134
|
+
console.error("[GitHub Endpoint] Stack:", error.stack);
|
|
135
|
+
next(error);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { GitHubClient } from "../github-client.js";
|
|
2
|
+
import * as utils from "../utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Featured repos controller
|
|
6
|
+
*/
|
|
7
|
+
export const featuredController = {
|
|
8
|
+
/**
|
|
9
|
+
* Get featured repos HTML page
|
|
10
|
+
* @type {import("express").RequestHandler}
|
|
11
|
+
*/
|
|
12
|
+
async get(request, response, next) {
|
|
13
|
+
try {
|
|
14
|
+
const { githubConfig } = request.app.locals.application;
|
|
15
|
+
|
|
16
|
+
if (!githubConfig) {
|
|
17
|
+
return response.status(500).render("github", {
|
|
18
|
+
title: "GitHub",
|
|
19
|
+
actions: [],
|
|
20
|
+
error: { message: "GitHub endpoint not configured correctly" },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { token, cacheTtl, featuredRepos } = githubConfig;
|
|
25
|
+
|
|
26
|
+
if (!featuredRepos || featuredRepos.length === 0) {
|
|
27
|
+
return response.render("featured", {
|
|
28
|
+
title: response.locals.__("github.featured.title"),
|
|
29
|
+
actions: [],
|
|
30
|
+
featured: [],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const client = new GitHubClient({ token, cacheTtl });
|
|
35
|
+
const featured = await fetchFeaturedRepos(client, featuredRepos);
|
|
36
|
+
|
|
37
|
+
response.render("featured", {
|
|
38
|
+
title: response.locals.__("github.featured.title"),
|
|
39
|
+
actions: [],
|
|
40
|
+
featured,
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("[GitHub Endpoint] Featured error:", error);
|
|
44
|
+
next(error);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get featured repos JSON API
|
|
50
|
+
* @type {import("express").RequestHandler}
|
|
51
|
+
*/
|
|
52
|
+
async api(request, response, next) {
|
|
53
|
+
try {
|
|
54
|
+
const { githubConfig } = request.app.locals.application;
|
|
55
|
+
|
|
56
|
+
if (!githubConfig) {
|
|
57
|
+
return response.status(500).json({ error: "GitHub not configured" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { token, cacheTtl, featuredRepos } = githubConfig;
|
|
61
|
+
|
|
62
|
+
if (!featuredRepos || featuredRepos.length === 0) {
|
|
63
|
+
return response.json({ featured: [] });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const client = new GitHubClient({ token, cacheTtl });
|
|
67
|
+
const featured = await fetchFeaturedRepos(client, featuredRepos);
|
|
68
|
+
|
|
69
|
+
response.json({ featured });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("[GitHub Endpoint] Featured API error:", error);
|
|
72
|
+
next(error);
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Fetch featured repos with commits
|
|
79
|
+
* @param {GitHubClient} client
|
|
80
|
+
* @param {string[]} featuredRepos
|
|
81
|
+
* @returns {Promise<Array>}
|
|
82
|
+
*/
|
|
83
|
+
async function fetchFeaturedRepos(client, featuredRepos) {
|
|
84
|
+
console.log("[GitHub Endpoint] Fetching featured repos:", featuredRepos);
|
|
85
|
+
|
|
86
|
+
const featuredPromises = featuredRepos.map(async (repoPath) => {
|
|
87
|
+
const [owner, repo] = repoPath.split("/");
|
|
88
|
+
try {
|
|
89
|
+
const [repoData, repoCommits] = await Promise.all([
|
|
90
|
+
client.getRepo(owner, repo),
|
|
91
|
+
client.getRepoCommits(owner, repo, 5),
|
|
92
|
+
]);
|
|
93
|
+
return {
|
|
94
|
+
...utils.formatRepos([repoData])[0],
|
|
95
|
+
commits: repoCommits.map((c) => ({
|
|
96
|
+
sha: c.sha.slice(0, 7),
|
|
97
|
+
message: utils.truncate(c.commit.message.split("\n")[0], 60),
|
|
98
|
+
url: c.html_url,
|
|
99
|
+
author: c.commit.author.name,
|
|
100
|
+
date: c.commit.author.date,
|
|
101
|
+
})),
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(
|
|
105
|
+
`[GitHub Endpoint] Error fetching ${repoPath}:`,
|
|
106
|
+
error.message,
|
|
107
|
+
);
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const featured = (await Promise.all(featuredPromises)).filter(Boolean);
|
|
113
|
+
console.log("[GitHub Endpoint] Featured repos loaded:", featured.length);
|
|
114
|
+
|
|
115
|
+
return featured;
|
|
116
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { GitHubClient } from "../github-client.js";
|
|
2
|
+
import * as utils from "../utils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Display starred repositories
|
|
6
|
+
* @type {import("express").RequestHandler}
|
|
7
|
+
*/
|
|
8
|
+
export const starsController = {
|
|
9
|
+
async get(request, response, next) {
|
|
10
|
+
try {
|
|
11
|
+
const { username, token, cacheTtl, limits } =
|
|
12
|
+
request.app.locals.application.githubConfig;
|
|
13
|
+
|
|
14
|
+
if (!username) {
|
|
15
|
+
return response.render("stars", {
|
|
16
|
+
title: response.locals.__("github.stars.title"),
|
|
17
|
+
error: { message: response.locals.__("github.error.noUsername") },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const client = new GitHubClient({ token, cacheTtl });
|
|
22
|
+
|
|
23
|
+
let starred = [];
|
|
24
|
+
try {
|
|
25
|
+
starred = await client.getUserStarred(username, limits.stars * 2);
|
|
26
|
+
} catch (apiError) {
|
|
27
|
+
console.error("GitHub API error:", apiError);
|
|
28
|
+
return response.render("stars", {
|
|
29
|
+
title: response.locals.__("github.stars.title"),
|
|
30
|
+
actions: [],
|
|
31
|
+
parent: {
|
|
32
|
+
href: request.baseUrl,
|
|
33
|
+
text: response.locals.__("github.title"),
|
|
34
|
+
},
|
|
35
|
+
error: { message: apiError.message || "Failed to fetch stars" },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const stars = utils.formatStarred(starred);
|
|
40
|
+
|
|
41
|
+
response.render("stars", {
|
|
42
|
+
title: response.locals.__("github.stars.title"),
|
|
43
|
+
actions: [],
|
|
44
|
+
parent: {
|
|
45
|
+
href: request.baseUrl,
|
|
46
|
+
text: response.locals.__("github.title"),
|
|
47
|
+
},
|
|
48
|
+
stars,
|
|
49
|
+
username,
|
|
50
|
+
mountPath: request.baseUrl,
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
next(error);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async api(request, response, next) {
|
|
58
|
+
try {
|
|
59
|
+
const { username, token, cacheTtl, limits } =
|
|
60
|
+
request.app.locals.application.githubConfig;
|
|
61
|
+
|
|
62
|
+
if (!username) {
|
|
63
|
+
return response.status(400).json({ error: "No username configured" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const client = new GitHubClient({ token, cacheTtl });
|
|
67
|
+
|
|
68
|
+
let starred = [];
|
|
69
|
+
try {
|
|
70
|
+
starred = await client.getUserStarred(username, limits.stars);
|
|
71
|
+
} catch (apiError) {
|
|
72
|
+
return response
|
|
73
|
+
.status(apiError.status || 500)
|
|
74
|
+
.json({ error: apiError.message });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const stars = utils.formatStarred(starred);
|
|
78
|
+
|
|
79
|
+
response.json({ stars });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
next(error);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { IndiekitError } from "@indiekit/error";
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://api.github.com";
|
|
4
|
+
|
|
5
|
+
export class GitHubClient {
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} options - Client options
|
|
8
|
+
* @param {string} [options.token] - GitHub personal access token
|
|
9
|
+
* @param {number} [options.cacheTtl] - Cache TTL in milliseconds
|
|
10
|
+
*/
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.token = options.token;
|
|
13
|
+
this.cacheTtl = options.cacheTtl || 900_000;
|
|
14
|
+
this.cache = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fetch from GitHub API with caching
|
|
19
|
+
* @param {string} endpoint - API endpoint
|
|
20
|
+
* @returns {Promise<object>} - Response data
|
|
21
|
+
*/
|
|
22
|
+
async fetch(endpoint) {
|
|
23
|
+
const url = `${BASE_URL}${endpoint}`;
|
|
24
|
+
|
|
25
|
+
// Check cache first
|
|
26
|
+
const cached = this.cache.get(url);
|
|
27
|
+
if (cached && Date.now() - cached.timestamp < this.cacheTtl) {
|
|
28
|
+
return cached.data;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const headers = {
|
|
32
|
+
Accept: "application/vnd.github+json",
|
|
33
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (this.token) {
|
|
37
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const response = await fetch(url, { headers });
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
throw await IndiekitError.fromFetch(response);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
|
|
48
|
+
// Cache result
|
|
49
|
+
this.cache.set(url, { data, timestamp: Date.now() });
|
|
50
|
+
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get user's events (commits, PRs, issues, etc.)
|
|
56
|
+
* Uses authenticated endpoint if token available (includes private activity)
|
|
57
|
+
* @param {string} username - GitHub username
|
|
58
|
+
* @param {number} [limit] - Number of events to fetch
|
|
59
|
+
* @returns {Promise<Array>} - User events
|
|
60
|
+
*/
|
|
61
|
+
async getUserEvents(username, limit = 30) {
|
|
62
|
+
// Use non-public endpoint when authenticated to include private repo activity
|
|
63
|
+
const endpoint = this.token
|
|
64
|
+
? `/users/${username}/events?per_page=${limit}`
|
|
65
|
+
: `/users/${username}/events/public?per_page=${limit}`;
|
|
66
|
+
return this.fetch(endpoint);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get commits for a specific repository
|
|
71
|
+
* @param {string} owner - Repository owner
|
|
72
|
+
* @param {string} repo - Repository name
|
|
73
|
+
* @param {number} [limit] - Number of commits to fetch
|
|
74
|
+
* @returns {Promise<Array>} - Repository commits
|
|
75
|
+
*/
|
|
76
|
+
async getRepoCommits(owner, repo, limit = 10) {
|
|
77
|
+
return this.fetch(`/repos/${owner}/${repo}/commits?per_page=${limit}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get user's starred repos
|
|
82
|
+
* @param {string} username - GitHub username
|
|
83
|
+
* @param {number} [limit] - Number of repos to fetch
|
|
84
|
+
* @returns {Promise<Array>} - Starred repositories
|
|
85
|
+
*/
|
|
86
|
+
async getUserStarred(username, limit = 30) {
|
|
87
|
+
return this.fetch(
|
|
88
|
+
`/users/${username}/starred?per_page=${limit}&sort=created`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get user profile
|
|
94
|
+
* @param {string} username - GitHub username
|
|
95
|
+
* @returns {Promise<object>} - User profile
|
|
96
|
+
*/
|
|
97
|
+
async getUser(username) {
|
|
98
|
+
return this.fetch(`/users/${username}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get user's repositories
|
|
103
|
+
* When authenticated, uses /user/repos to include private repos
|
|
104
|
+
* @param {string} username - GitHub username
|
|
105
|
+
* @param {number} [limit] - Number of repos to fetch
|
|
106
|
+
* @param {string} [sort] - Sort by: created, updated, pushed, full_name
|
|
107
|
+
* @returns {Promise<Array>} - User repositories
|
|
108
|
+
*/
|
|
109
|
+
async getUserRepos(username, limit = 30, sort = "pushed") {
|
|
110
|
+
// When authenticated, use /user/repos for private repos access
|
|
111
|
+
// Then filter by owner to get only the user's repos (not org repos)
|
|
112
|
+
if (this.token) {
|
|
113
|
+
const repos = await this.fetch(
|
|
114
|
+
`/user/repos?per_page=${limit}&sort=${sort}&direction=desc&affiliation=owner`,
|
|
115
|
+
);
|
|
116
|
+
return repos;
|
|
117
|
+
}
|
|
118
|
+
// Unauthenticated: public repos only
|
|
119
|
+
return this.fetch(
|
|
120
|
+
`/users/${username}/repos?per_page=${limit}&sort=${sort}&direction=desc`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get repo details
|
|
126
|
+
* @param {string} owner - Repository owner
|
|
127
|
+
* @param {string} repo - Repository name
|
|
128
|
+
* @returns {Promise<object>} - Repository details
|
|
129
|
+
*/
|
|
130
|
+
async getRepo(owner, repo) {
|
|
131
|
+
return this.fetch(`/repos/${owner}/${repo}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get repo events (activity from others)
|
|
136
|
+
* @param {string} owner - Repository owner
|
|
137
|
+
* @param {string} repo - Repository name
|
|
138
|
+
* @param {number} [limit] - Number of events to fetch
|
|
139
|
+
* @returns {Promise<Array>} - Repository events
|
|
140
|
+
*/
|
|
141
|
+
async getRepoEvents(owner, repo, limit = 30) {
|
|
142
|
+
return this.fetch(`/repos/${owner}/${repo}/events?per_page=${limit}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get user's PRs across all repos
|
|
147
|
+
* @param {string} username - GitHub username
|
|
148
|
+
* @param {number} [limit] - Number of PRs to fetch
|
|
149
|
+
* @returns {Promise<object>} - Search results with PRs
|
|
150
|
+
*/
|
|
151
|
+
async getUserPRs(username, limit = 30) {
|
|
152
|
+
return this.fetch(
|
|
153
|
+
`/search/issues?q=author:${username}+type:pr&per_page=${limit}&sort=created`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get user's issues across all repos
|
|
159
|
+
* @param {string} username - GitHub username
|
|
160
|
+
* @param {number} [limit] - Number of issues to fetch
|
|
161
|
+
* @returns {Promise<object>} - Search results with issues
|
|
162
|
+
*/
|
|
163
|
+
async getUserIssues(username, limit = 30) {
|
|
164
|
+
return this.fetch(
|
|
165
|
+
`/search/issues?q=author:${username}+type:issue&per_page=${limit}&sort=created`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|