ghost-github-portfolio 0.2.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/LICENSE +674 -0
- package/README.md +210 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +116 -0
- package/dist/config.js.map +1 -0
- package/dist/generator.d.ts +5 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +204 -0
- package/dist/generator.js.map +1 -0
- package/dist/ghost.d.ts +12 -0
- package/dist/ghost.d.ts.map +1 -0
- package/dist/ghost.js +71 -0
- package/dist/ghost.js.map +1 -0
- package/dist/github.d.ts +9 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +107 -0
- package/dist/github.js.map +1 -0
- package/dist/http.d.ts +14 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +64 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
package/dist/github.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Config, GitHubRepo } from "./types.js";
|
|
2
|
+
import { type RateLimitInfo } from "./http.js";
|
|
3
|
+
export interface FetchReposResult {
|
|
4
|
+
repos: GitHubRepo[];
|
|
5
|
+
rateLimit: RateLimitInfo | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function fetchRepos(config: Config, verbose?: boolean): Promise<GitHubRepo[]>;
|
|
8
|
+
export declare function detectBanner(repo: GitHubRepo, config: Config): Promise<string | null>;
|
|
9
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AAanB,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;CACjC;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,UAAQ,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CAwDvB;AAaD,wBAAsB,YAAY,CAChC,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB"}
|
package/dist/github.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { fetchWithRetry, parseRateLimitHeaders, } from "./http.js";
|
|
2
|
+
const GITHUB_API = "https://api.github.com";
|
|
3
|
+
function headers(token) {
|
|
4
|
+
const h = {
|
|
5
|
+
Accept: "application/vnd.github+json",
|
|
6
|
+
"User-Agent": "ghost-github-portfolio",
|
|
7
|
+
};
|
|
8
|
+
if (token)
|
|
9
|
+
h.Authorization = `Bearer ${token}`;
|
|
10
|
+
return h;
|
|
11
|
+
}
|
|
12
|
+
export async function fetchRepos(config, verbose = false) {
|
|
13
|
+
const { username, token } = config.github;
|
|
14
|
+
const { minStars, maxRepos, excludeRepos, includeForked } = config.portfolio;
|
|
15
|
+
const allRepos = [];
|
|
16
|
+
let page = 1;
|
|
17
|
+
const perPage = 100;
|
|
18
|
+
let lastRateLimit = null;
|
|
19
|
+
const onRetry = verbose
|
|
20
|
+
? (_attempt, _delay, reason) => console.log(` [retry] ${reason}`)
|
|
21
|
+
: undefined;
|
|
22
|
+
// GitHub REST API /users/{user}/repos does not support sort=stars.
|
|
23
|
+
// Fetch all pages, then sort client-side.
|
|
24
|
+
while (true) {
|
|
25
|
+
const url = `${GITHUB_API}/users/${username}/repos?per_page=${perPage}&page=${page}&type=owner`;
|
|
26
|
+
const res = await fetchWithRetry(url, { headers: headers(token) }, { onRetry });
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const body = await res.text();
|
|
29
|
+
throw new Error(`GitHub API error ${res.status}: ${body}`);
|
|
30
|
+
}
|
|
31
|
+
lastRateLimit = parseRateLimitHeaders(res);
|
|
32
|
+
if (verbose && lastRateLimit) {
|
|
33
|
+
console.log(` [rate-limit] ${lastRateLimit.remaining}/${lastRateLimit.limit} remaining (resets ${lastRateLimit.resetAt.toISOString()})`);
|
|
34
|
+
}
|
|
35
|
+
const repos = (await res.json());
|
|
36
|
+
if (repos.length === 0)
|
|
37
|
+
break;
|
|
38
|
+
allRepos.push(...repos);
|
|
39
|
+
// If fewer than perPage returned, we've reached the last page
|
|
40
|
+
if (repos.length < perPage)
|
|
41
|
+
break;
|
|
42
|
+
page++;
|
|
43
|
+
}
|
|
44
|
+
const excludeSet = new Set(excludeRepos.map((r) => r.toLowerCase()));
|
|
45
|
+
return allRepos
|
|
46
|
+
.filter((r) => r.stargazers_count >= minStars)
|
|
47
|
+
.filter((r) => includeForked || !r.fork)
|
|
48
|
+
.filter((r) => !excludeSet.has(r.name.toLowerCase()))
|
|
49
|
+
.filter((r) => !config.portfolio.repos[r.name]?.exclude)
|
|
50
|
+
.sort((a, b) => b.stargazers_count - a.stargazers_count)
|
|
51
|
+
.slice(0, maxRepos);
|
|
52
|
+
}
|
|
53
|
+
const BANNER_CANDIDATES = [
|
|
54
|
+
"docs/images/banner.svg",
|
|
55
|
+
"docs/images/banner.png",
|
|
56
|
+
"media/banner.svg",
|
|
57
|
+
"media/banner.png",
|
|
58
|
+
"assets/banner.svg",
|
|
59
|
+
"assets/banner.png",
|
|
60
|
+
"banner.svg",
|
|
61
|
+
"banner.png",
|
|
62
|
+
];
|
|
63
|
+
export async function detectBanner(repo, config) {
|
|
64
|
+
if (!config.portfolio.showBanner)
|
|
65
|
+
return null;
|
|
66
|
+
try {
|
|
67
|
+
const { username } = config.github;
|
|
68
|
+
const branch = repo.default_branch;
|
|
69
|
+
// Config override for this specific repo
|
|
70
|
+
const overridePath = config.portfolio.repos[repo.name]?.bannerPath ??
|
|
71
|
+
config.portfolio.bannerPaths[repo.name];
|
|
72
|
+
if (overridePath) {
|
|
73
|
+
const url = `https://raw.githubusercontent.com/${username}/${repo.name}/${branch}/${overridePath}`;
|
|
74
|
+
if (await urlExists(url))
|
|
75
|
+
return url;
|
|
76
|
+
}
|
|
77
|
+
// Try default path
|
|
78
|
+
const defaultUrl = `https://raw.githubusercontent.com/${username}/${repo.name}/${branch}/${config.portfolio.defaultBannerPath}`;
|
|
79
|
+
if (await urlExists(defaultUrl))
|
|
80
|
+
return defaultUrl;
|
|
81
|
+
// Try candidates
|
|
82
|
+
for (const candidate of BANNER_CANDIDATES) {
|
|
83
|
+
if (candidate === config.portfolio.defaultBannerPath)
|
|
84
|
+
continue;
|
|
85
|
+
const url = `https://raw.githubusercontent.com/${username}/${repo.name}/${branch}/${candidate}`;
|
|
86
|
+
if (await urlExists(url))
|
|
87
|
+
return url;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Banner detection is non-critical; skip banner on persistent errors
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function urlExists(url) {
|
|
97
|
+
const res = await fetchWithRetry(url, {
|
|
98
|
+
method: "HEAD",
|
|
99
|
+
redirect: "follow",
|
|
100
|
+
});
|
|
101
|
+
if (res.ok)
|
|
102
|
+
return true;
|
|
103
|
+
if (res.status === 404)
|
|
104
|
+
return false;
|
|
105
|
+
throw new Error(`Banner check failed for ${url}: HTTP ${res.status}`);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,qBAAqB,GAEtB,MAAM,WAAW,CAAC;AAEnB,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAE5C,SAAS,OAAO,CAAC,KAAc;IAC7B,MAAM,CAAC,GAA2B;QAChC,MAAM,EAAE,6BAA6B;QACrC,YAAY,EAAE,wBAAwB;KACvC,CAAC;IACF,IAAI,KAAK;QAAE,CAAC,CAAC,aAAa,GAAG,UAAU,KAAK,EAAE,CAAC;IAC/C,OAAO,CAAC,CAAC;AACX,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAAc,EACd,OAAO,GAAG,KAAK;IAEf,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IAC1C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC;IAE7E,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,OAAO,GAAG,GAAG,CAAC;IACpB,IAAI,aAAa,GAAyB,IAAI,CAAC;IAE/C,MAAM,OAAO,GAAG,OAAO;QACrB,CAAC,CAAC,CAAC,QAAgB,EAAE,MAAc,EAAE,MAAc,EAAE,EAAE,CACnD,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,EAAE,CAAC;QACtC,CAAC,CAAC,SAAS,CAAC;IAEd,mEAAmE;IACnE,0CAA0C;IAC1C,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,GAAG,UAAU,UAAU,QAAQ,mBAAmB,OAAO,SAAS,IAAI,aAAa,CAAC;QAChG,MAAM,GAAG,GAAG,MAAM,cAAc,CAC9B,GAAG,EACH,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,EAC3B,EAAE,OAAO,EAAE,CACZ,CAAC;QAEF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,aAAa,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,OAAO,IAAI,aAAa,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CACT,kBAAkB,aAAa,CAAC,SAAS,IAAI,aAAa,CAAC,KAAK,sBAAsB,aAAa,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAC7H,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAiB,CAAC;QACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM;QAE9B,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAExB,8DAA8D;QAC9D,IAAI,KAAK,CAAC,MAAM,GAAG,OAAO;YAAE,MAAM;QAElC,IAAI,EAAE,CAAC;IACT,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAErE,OAAO,QAAQ;SACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,IAAI,QAAQ,CAAC;SAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;SACvC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;SACpD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;SACvD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,CAAC,gBAAgB,CAAC;SACvD,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,iBAAiB,GAAG;IACxB,wBAAwB;IACxB,wBAAwB;IACxB,kBAAkB;IAClB,kBAAkB;IAClB,mBAAmB;IACnB,mBAAmB;IACnB,YAAY;IACZ,YAAY;CACb,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAgB,EAChB,MAAc;IAEd,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC;QAEnC,yCAAyC;QACzC,MAAM,YAAY,GAChB,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU;YAC7C,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE1C,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,qCAAqC,QAAQ,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,YAAY,EAAE,CAAC;YACnG,IAAI,MAAM,SAAS,CAAC,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC;QACvC,CAAC;QAED,mBAAmB;QACnB,MAAM,UAAU,GAAG,qCAAqC,QAAQ,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,CAAC,iBAAiB,EAAE,CAAC;QAChI,IAAI,MAAM,SAAS,CAAC,UAAU,CAAC;YAAE,OAAO,UAAU,CAAC;QAEnD,iBAAiB;QACjB,KAAK,MAAM,SAAS,IAAI,iBAAiB,EAAE,CAAC;YAC1C,IAAI,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC,iBAAiB;gBAAE,SAAS;YAC/D,MAAM,GAAG,GAAG,qCAAqC,QAAQ,IAAI,IAAI,CAAC,IAAI,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;YAChG,IAAI,MAAM,SAAS,CAAC,GAAG,CAAC;gBAAE,OAAO,GAAG,CAAC;QACvC,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,GAAW;IAClC,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE;QACpC,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,QAAQ;KACnB,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,UAAU,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;AACxE,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface RetryOptions {
|
|
2
|
+
maxRetries?: number;
|
|
3
|
+
baseDelay?: number;
|
|
4
|
+
onRetry?: (attempt: number, delay: number, reason: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function fetchWithRetry(url: string, init?: RequestInit, opts?: RetryOptions): Promise<Response>;
|
|
7
|
+
export declare function parseRateLimitHeaders(res: Response): RateLimitInfo | null;
|
|
8
|
+
export interface RateLimitInfo {
|
|
9
|
+
limit: number;
|
|
10
|
+
remaining: number;
|
|
11
|
+
resetAt: Date;
|
|
12
|
+
}
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAGA,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACpE;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,WAAW,EAClB,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,QAAQ,CAAC,CAmEnB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,QAAQ,GAAG,aAAa,GAAG,IAAI,CAYzE;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,IAAI,CAAC;CACf"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const MAX_RETRIES = 3;
|
|
2
|
+
const BASE_DELAY_MS = 1000;
|
|
3
|
+
export async function fetchWithRetry(url, init, opts) {
|
|
4
|
+
const maxRetries = opts?.maxRetries ?? MAX_RETRIES;
|
|
5
|
+
const baseDelay = opts?.baseDelay ?? BASE_DELAY_MS;
|
|
6
|
+
let lastError;
|
|
7
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
8
|
+
let res;
|
|
9
|
+
try {
|
|
10
|
+
res = await fetch(url, init);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
lastError = error;
|
|
14
|
+
if (attempt >= maxRetries)
|
|
15
|
+
break;
|
|
16
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
17
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
18
|
+
opts?.onRetry?.(attempt + 1, delay, `Network error: ${message}. Retrying in ${delay}ms`);
|
|
19
|
+
await sleep(delay);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
// Rate limit handling (GitHub API)
|
|
23
|
+
if (res.status === 403 || res.status === 429) {
|
|
24
|
+
const rateLimitRemaining = res.headers.get("x-ratelimit-remaining");
|
|
25
|
+
const rateLimitReset = res.headers.get("x-ratelimit-reset");
|
|
26
|
+
if (rateLimitRemaining === "0" && rateLimitReset) {
|
|
27
|
+
const resetTime = parseInt(rateLimitReset, 10) * 1000;
|
|
28
|
+
const waitMs = Math.max(resetTime - Date.now(), 0);
|
|
29
|
+
if (waitMs > 0 && waitMs < 120_000 && attempt < maxRetries) {
|
|
30
|
+
const waitSec = Math.ceil(waitMs / 1000);
|
|
31
|
+
opts?.onRetry?.(attempt + 1, waitMs, `Rate limited. Waiting ${waitSec}s for reset`);
|
|
32
|
+
await sleep(waitMs);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Retry on server errors
|
|
38
|
+
if (res.status >= 500 && attempt < maxRetries) {
|
|
39
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
40
|
+
opts?.onRetry?.(attempt + 1, delay, `Server error ${res.status}. Retrying in ${delay}ms`);
|
|
41
|
+
await sleep(delay);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
return res;
|
|
45
|
+
}
|
|
46
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
47
|
+
throw new Error(`Request to ${url} failed after ${maxRetries + 1} attempts: ${message}`);
|
|
48
|
+
}
|
|
49
|
+
export function parseRateLimitHeaders(res) {
|
|
50
|
+
const limit = res.headers.get("x-ratelimit-limit");
|
|
51
|
+
const remaining = res.headers.get("x-ratelimit-remaining");
|
|
52
|
+
const reset = res.headers.get("x-ratelimit-reset");
|
|
53
|
+
if (!limit || !remaining || !reset)
|
|
54
|
+
return null;
|
|
55
|
+
return {
|
|
56
|
+
limit: parseInt(limit, 10),
|
|
57
|
+
remaining: parseInt(remaining, 10),
|
|
58
|
+
resetAt: new Date(parseInt(reset, 10) * 1000),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function sleep(ms) {
|
|
62
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { writeFileSync } from "node:fs";
|
|
4
|
+
import { loadConfig, generateExampleConfig } from "./config.js";
|
|
5
|
+
import { fetchRepos, detectBanner } from "./github.js";
|
|
6
|
+
import { fetchPage, updatePage } from "./ghost.js";
|
|
7
|
+
import { generateCard, generateFooter, buildLexical } from "./generator.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("ghost-github-portfolio")
|
|
11
|
+
.description("Auto-sync GitHub repositories to a Ghost CMS portfolio page. Fetches repos, sorts by stars, generates cards with banners and badges, and updates Ghost via the Admin API.")
|
|
12
|
+
.version("0.2.0");
|
|
13
|
+
program
|
|
14
|
+
.command("sync")
|
|
15
|
+
.description("Sync GitHub repos to your Ghost portfolio page")
|
|
16
|
+
.requiredOption("-c, --config <path>", "Path to config YAML file")
|
|
17
|
+
.option("--dry-run", "Output generated HTML without updating Ghost")
|
|
18
|
+
.option("--json", "Output raw lexical JSON (implies --dry-run)")
|
|
19
|
+
.option("-v, --verbose", "Show detailed progress")
|
|
20
|
+
.action(async (opts) => {
|
|
21
|
+
try {
|
|
22
|
+
const config = loadConfig(opts.config);
|
|
23
|
+
const verbose = opts.verbose || false;
|
|
24
|
+
const dryRun = opts.dryRun || opts.json || false;
|
|
25
|
+
// Fetch repos
|
|
26
|
+
if (verbose)
|
|
27
|
+
console.log(`Fetching repos for ${config.github.username} (min ${config.portfolio.minStars} stars)...`);
|
|
28
|
+
const repos = await fetchRepos(config);
|
|
29
|
+
console.log(`Found ${repos.length} repos matching criteria (${config.portfolio.minStars}+ stars)`);
|
|
30
|
+
if (repos.length === 0) {
|
|
31
|
+
console.log("No repos found. Check your config.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Detect banners (parallel)
|
|
35
|
+
if (verbose)
|
|
36
|
+
console.log("Detecting banner images...");
|
|
37
|
+
const bannerResults = await Promise.all(repos.map(async (repo) => {
|
|
38
|
+
const banner = await detectBanner(repo, config);
|
|
39
|
+
if (verbose) {
|
|
40
|
+
console.log(` ${repo.name}: ${banner ? "found" : "no banner"}`);
|
|
41
|
+
}
|
|
42
|
+
return { repo, banner };
|
|
43
|
+
}));
|
|
44
|
+
// Generate cards
|
|
45
|
+
const cards = bannerResults.map(({ repo, banner }) => generateCard(repo, banner, config));
|
|
46
|
+
const footer = generateFooter(repos, config);
|
|
47
|
+
const lexical = buildLexical(cards, footer);
|
|
48
|
+
if (opts.json) {
|
|
49
|
+
console.log(JSON.stringify(lexical, null, 2));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
console.log("\n--- DRY RUN: Generated HTML ---\n");
|
|
54
|
+
for (const { repo, banner } of bannerResults) {
|
|
55
|
+
console.log(`${repo.name} (${repo.stargazers_count} stars) ${banner ? "[banner]" : "[no banner]"}`);
|
|
56
|
+
}
|
|
57
|
+
console.log(`\nTotal cards: ${cards.length}`);
|
|
58
|
+
console.log(`Total stars: ${repos.reduce((s, r) => s + r.stargazers_count, 0)}`);
|
|
59
|
+
// Write to temp file for inspection
|
|
60
|
+
const tmpPath = "/tmp/ghost-portfolio-preview.html";
|
|
61
|
+
const htmlPreview = cards.join("\n<br>\n");
|
|
62
|
+
writeFileSync(tmpPath, htmlPreview);
|
|
63
|
+
console.log(`\nPreview written to ${tmpPath}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Fetch current page
|
|
67
|
+
if (verbose)
|
|
68
|
+
console.log("Fetching Ghost page...");
|
|
69
|
+
const page = await fetchPage(config);
|
|
70
|
+
if (verbose)
|
|
71
|
+
console.log(` Page: ${page.title} (${page.id})`);
|
|
72
|
+
// Update page
|
|
73
|
+
if (verbose)
|
|
74
|
+
console.log("Updating Ghost page...");
|
|
75
|
+
const updated = await updatePage(config, page.id, page.updated_at, lexical);
|
|
76
|
+
console.log(`Portfolio updated: ${updated.title}`);
|
|
77
|
+
console.log(` ${repos.length} projects displayed`);
|
|
78
|
+
console.log(` ${repos.reduce((s, r) => s + r.stargazers_count, 0)} total stars`);
|
|
79
|
+
console.log(` ${bannerResults.filter((b) => b.banner).length} banners loaded`);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
program
|
|
87
|
+
.command("init")
|
|
88
|
+
.description("Generate an example config file")
|
|
89
|
+
.option("-o, --output <path>", "Output path", "config.yml")
|
|
90
|
+
.action((opts) => {
|
|
91
|
+
writeFileSync(opts.output, generateExampleConfig());
|
|
92
|
+
console.log(`Example config written to ${opts.output}`);
|
|
93
|
+
console.log("Edit the file with your GitHub username and Ghost API key.");
|
|
94
|
+
});
|
|
95
|
+
program.parse();
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
github: {
|
|
3
|
+
username: string;
|
|
4
|
+
token?: string;
|
|
5
|
+
};
|
|
6
|
+
ghost: {
|
|
7
|
+
url: string;
|
|
8
|
+
adminApiKey: string;
|
|
9
|
+
pageId?: string;
|
|
10
|
+
pageSlug?: string;
|
|
11
|
+
};
|
|
12
|
+
portfolio: PortfolioConfig;
|
|
13
|
+
}
|
|
14
|
+
export interface PortfolioConfig {
|
|
15
|
+
minStars: number;
|
|
16
|
+
maxRepos: number;
|
|
17
|
+
excludeRepos: string[];
|
|
18
|
+
includeForked: boolean;
|
|
19
|
+
badgeStyle: string;
|
|
20
|
+
showBanner: boolean;
|
|
21
|
+
centerContent: boolean;
|
|
22
|
+
defaultBannerPath: string;
|
|
23
|
+
bannerPaths: Record<string, string>;
|
|
24
|
+
repos: Record<string, RepoOverride>;
|
|
25
|
+
footer: FooterConfig;
|
|
26
|
+
}
|
|
27
|
+
export interface FooterConfig {
|
|
28
|
+
showStats: boolean;
|
|
29
|
+
showViewAll: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface RepoOverride {
|
|
32
|
+
bannerPath?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
dockerImage?: string;
|
|
35
|
+
badges?: CustomBadge[];
|
|
36
|
+
keyFeatures?: string[];
|
|
37
|
+
techStack?: string;
|
|
38
|
+
exclude?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface CustomBadge {
|
|
41
|
+
type: "website" | "docker" | "awesome-list" | "platform" | "docs" | "custom";
|
|
42
|
+
url?: string;
|
|
43
|
+
label?: string;
|
|
44
|
+
value?: string;
|
|
45
|
+
color?: string;
|
|
46
|
+
logo?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface GitHubRepo {
|
|
49
|
+
name: string;
|
|
50
|
+
full_name: string;
|
|
51
|
+
html_url: string;
|
|
52
|
+
description: string | null;
|
|
53
|
+
stargazers_count: number;
|
|
54
|
+
forks_count: number;
|
|
55
|
+
license: {
|
|
56
|
+
spdx_id: string;
|
|
57
|
+
} | null;
|
|
58
|
+
fork: boolean;
|
|
59
|
+
homepage: string | null;
|
|
60
|
+
topics: string[];
|
|
61
|
+
language: string | null;
|
|
62
|
+
default_branch: string;
|
|
63
|
+
}
|
|
64
|
+
export interface LexicalDocument {
|
|
65
|
+
root: {
|
|
66
|
+
children: LexicalNode[];
|
|
67
|
+
direction: string;
|
|
68
|
+
format: string;
|
|
69
|
+
indent: number;
|
|
70
|
+
type: string;
|
|
71
|
+
version: number;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export type LexicalNode = HtmlNode | HorizontalRuleNode;
|
|
75
|
+
export interface HtmlNode {
|
|
76
|
+
type: "html";
|
|
77
|
+
version: number;
|
|
78
|
+
html: string;
|
|
79
|
+
}
|
|
80
|
+
export interface HorizontalRuleNode {
|
|
81
|
+
type: "horizontalrule";
|
|
82
|
+
version: number;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ghost-github-portfolio",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Auto-sync GitHub repositories to a Ghost CMS portfolio page. Fetches repos, sorts by stars, generates cards with banners and badges, and updates Ghost via the Admin API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ghost-github-portfolio": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"start": "node dist/index.js",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"lint": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"ghost",
|
|
23
|
+
"github",
|
|
24
|
+
"portfolio",
|
|
25
|
+
"cms",
|
|
26
|
+
"sync",
|
|
27
|
+
"auto-update",
|
|
28
|
+
"badges",
|
|
29
|
+
"banners",
|
|
30
|
+
"stars",
|
|
31
|
+
"developer-portfolio",
|
|
32
|
+
"ghost-cms",
|
|
33
|
+
"admin-api",
|
|
34
|
+
"lexical",
|
|
35
|
+
"self-hosted"
|
|
36
|
+
],
|
|
37
|
+
"author": "GeiserX",
|
|
38
|
+
"license": "GPL-3.0",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/GeiserX/ghost-github-portfolio.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/GeiserX/ghost-github-portfolio",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/GeiserX/ghost-github-portfolio/issues"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"commander": "^13.1.0",
|
|
52
|
+
"yaml": "^2.7.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.15.3",
|
|
56
|
+
"tsx": "^4.19.4",
|
|
57
|
+
"typescript": "^5.8.3",
|
|
58
|
+
"vitest": "^3.1.3"
|
|
59
|
+
}
|
|
60
|
+
}
|