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.
@@ -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":"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"}
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }