ghost-github-portfolio 0.3.5 → 0.4.1
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 +25 -184
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +91 -63
- package/dist/config.js.map +1 -1
- package/dist/generator.d.ts +36 -2
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +181 -180
- package/dist/generator.js.map +1 -1
- package/dist/ghost.d.ts +8 -0
- package/dist/ghost.d.ts.map +1 -1
- package/dist/ghost.js +35 -19
- package/dist/ghost.js.map +1 -1
- package/dist/github.d.ts +26 -7
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +91 -63
- package/dist/github.js.map +1 -1
- package/dist/http.d.ts +9 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +47 -1
- package/dist/http.js.map +1 -1
- package/dist/index.js +55 -56
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +15 -25
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,210 +1,51 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="docs/images/banner.svg" alt="Ghost GitHub Portfolio
|
|
2
|
+
<img src="https://raw.githubusercontent.com/GeiserX/ghost-github-portfolio/main/docs/images/banner.svg" alt="Ghost GitHub Portfolio" width="900"/>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/ghost-github-portfolio"><img src="https://img.shields.io/npm/v/ghost-github-portfolio?style=flat-square&logo=npm" alt="npm"></a>
|
|
7
|
-
<a href="https://hub.docker.com/r/drumsergio/ghost-github-portfolio"><img src="https://img.shields.io/docker/pulls/drumsergio/ghost-github-portfolio?style=flat-square&logo=docker" alt="Docker Pulls"></a>
|
|
8
7
|
<a href="https://github.com/GeiserX/ghost-github-portfolio/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/GeiserX/ghost-github-portfolio/ci.yml?style=flat-square&logo=github&label=CI" alt="CI"></a>
|
|
9
|
-
<a href="LICENSE"><img src="https://img.shields.io/github/license/GeiserX/ghost-github-portfolio?style=flat-square" alt="License"></a>
|
|
10
|
-
<a href="https://
|
|
8
|
+
<a href="https://github.com/GeiserX/ghost-github-portfolio/blob/main/LICENSE"><img src="https://img.shields.io/github/license/GeiserX/ghost-github-portfolio?style=flat-square" alt="License"></a>
|
|
9
|
+
<a href="https://hub.docker.com/r/drumsergio/ghost-github-portfolio"><img src="https://img.shields.io/docker/pulls/drumsergio/ghost-github-portfolio?style=flat-square&logo=docker" alt="Docker Pulls"></a>
|
|
10
|
+
<a href="https://codecov.io/gh/GeiserX/ghost-github-portfolio"><img src="https://codecov.io/gh/GeiserX/ghost-github-portfolio/graph/badge.svg" alt="codecov"></a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
<p align="center"><strong>
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## What it does
|
|
13
|
+
<p align="center"><strong>Keep a Ghost portfolio page in step with your GitHub repositories.</strong></p>
|
|
18
14
|
|
|
19
|
-
`ghost-github-portfolio`
|
|
15
|
+
`ghost-github-portfolio` reads your public GitHub repositories, keeps the most starred ones, and writes them to a Ghost page as a grid of cards through the Ghost Admin API. Run it as a CLI, in Docker, or as a GitHub Action on a schedule, and the page follows your star counts.
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
<p align="center"><img src="https://raw.githubusercontent.com/GeiserX/ghost-github-portfolio/main/docs/images/screenshots/preview-desktop.png" alt="Portfolio page preview" width="900"/></p>
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
## Features
|
|
24
20
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
21
|
+
- One Ghost html card with its own scoped style: an intro, a card per repository, and a line pointing at your GitHub profile.
|
|
22
|
+
- Each card has the repository banner (or a name tile), the name and star count, one line on what it does, and a "Site" link.
|
|
23
|
+
- Three columns on desktop, two on tablets, one on phones; wide cards on Alto, Source and Casper.
|
|
24
|
+
- Filters by stars, forks, archived, name patterns and awesome lists, then cuts to `maxRepos`.
|
|
25
|
+
- Per-repo overrides in `config.yml` or in a `.ghost-portfolio.yml` inside each repository.
|
|
26
|
+
- Checks that each SVG banner is self-contained, and warns when it falls back to a name tile.
|
|
27
|
+
- Can write through the Ghost origin when a firewall blocks `<img` in request bodies.
|
|
28
|
+
- `--dry-run`, `--json` and `--html-out` previews that never touch Ghost.
|
|
32
29
|
|
|
33
30
|
## Quick start
|
|
34
31
|
|
|
35
|
-
### 1. Install
|
|
36
|
-
|
|
37
32
|
```bash
|
|
38
|
-
# npm (global)
|
|
39
33
|
npm install -g ghost-github-portfolio
|
|
40
|
-
|
|
41
|
-
# npx (no install)
|
|
42
|
-
npx ghost-github-portfolio
|
|
43
|
-
|
|
44
|
-
# Docker
|
|
45
|
-
docker pull drumsergio/ghost-github-portfolio:0.3.0
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### 2. Create a config
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
34
|
ghost-github-portfolio init
|
|
35
|
+
ghost-github-portfolio sync --config config.yml --html-out preview.html
|
|
52
36
|
```
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### 3. Get your Ghost Admin API key
|
|
57
|
-
|
|
58
|
-
1. Go to your Ghost Admin panel > **Settings** > **Integrations**
|
|
59
|
-
2. Create a new **Custom Integration**
|
|
60
|
-
3. Copy the **Admin API Key** (format: `KEY_ID:SECRET`)
|
|
61
|
-
|
|
62
|
-
### 4. Run
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
# Preview (no changes to Ghost)
|
|
66
|
-
ghost-github-portfolio sync --config config.yml --dry-run --verbose
|
|
67
|
-
|
|
68
|
-
# Sync to Ghost
|
|
69
|
-
ghost-github-portfolio sync --config config.yml --verbose
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Configuration
|
|
73
|
-
|
|
74
|
-
```yaml
|
|
75
|
-
github:
|
|
76
|
-
username: YOUR_GITHUB_USERNAME
|
|
77
|
-
# token: ghp_xxx # Optional: higher rate limits (env: GHOST_GITHUB_TOKEN)
|
|
78
|
-
|
|
79
|
-
ghost:
|
|
80
|
-
url: https://your-ghost-blog.com
|
|
81
|
-
adminApiKey: "KEY_ID:SECRET_HEX"
|
|
82
|
-
pageSlug: portfolio # or pageId: "hex_id"
|
|
83
|
-
|
|
84
|
-
portfolio:
|
|
85
|
-
minStars: 2
|
|
86
|
-
maxRepos: 50
|
|
87
|
-
includeForked: false
|
|
88
|
-
badgeStyle: for-the-badge # flat, flat-square, for-the-badge, plastic, social
|
|
89
|
-
showBanner: true
|
|
90
|
-
centerContent: true
|
|
91
|
-
defaultBannerPath: docs/images/banner.svg
|
|
92
|
-
|
|
93
|
-
bannerPaths:
|
|
94
|
-
awesome-spain: media/banner.svg # Override per repo
|
|
95
|
-
|
|
96
|
-
excludeRepos:
|
|
97
|
-
- .github
|
|
98
|
-
|
|
99
|
-
repos:
|
|
100
|
-
my-project:
|
|
101
|
-
description: "Custom description"
|
|
102
|
-
dockerImage: myuser/my-project # Adds Docker pulls badge
|
|
103
|
-
techStack: "Python, Docker, Redis"
|
|
104
|
-
badges:
|
|
105
|
-
- type: website
|
|
106
|
-
url: https://my-project.com
|
|
107
|
-
- type: awesome-list
|
|
108
|
-
- type: platform
|
|
109
|
-
label: macOS
|
|
110
|
-
logo: apple
|
|
111
|
-
- type: docs
|
|
112
|
-
url: https://docs.my-project.com
|
|
113
|
-
- type: custom
|
|
114
|
-
label: MCP
|
|
115
|
-
value: Official Registry
|
|
116
|
-
color: E6522C
|
|
117
|
-
|
|
118
|
-
footer:
|
|
119
|
-
showStats: true
|
|
120
|
-
showViewAll: true
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Environment variables
|
|
124
|
-
|
|
125
|
-
| Variable | Description |
|
|
126
|
-
|----------|-------------|
|
|
127
|
-
| `GHOST_GITHUB_TOKEN` | GitHub token for private repos / higher rate limits |
|
|
128
|
-
| `GHOST_ADMIN_API_KEY` | Ghost Admin API key (overrides config file) |
|
|
129
|
-
|
|
130
|
-
## Docker
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
docker run --rm \
|
|
134
|
-
-v /path/to/config.yml:/config/config.yml \
|
|
135
|
-
drumsergio/ghost-github-portfolio:0.3.0
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Docker Compose (daily cron)
|
|
139
|
-
|
|
140
|
-
```yaml
|
|
141
|
-
services:
|
|
142
|
-
ghost-portfolio:
|
|
143
|
-
image: drumsergio/ghost-github-portfolio:0.3.0
|
|
144
|
-
volumes:
|
|
145
|
-
- ./config.yml:/config/config.yml:ro
|
|
146
|
-
# Run daily at 6 AM via external cron or restart policy
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## GitHub Action
|
|
150
|
-
|
|
151
|
-
Create `.github/workflows/portfolio.yml` in any repo:
|
|
152
|
-
|
|
153
|
-
```yaml
|
|
154
|
-
name: Update Portfolio
|
|
155
|
-
|
|
156
|
-
on:
|
|
157
|
-
schedule:
|
|
158
|
-
- cron: "0 6 * * *" # Daily at 6 AM UTC
|
|
159
|
-
workflow_dispatch: # Manual trigger
|
|
160
|
-
|
|
161
|
-
jobs:
|
|
162
|
-
sync:
|
|
163
|
-
runs-on: ubuntu-latest
|
|
164
|
-
steps:
|
|
165
|
-
- uses: actions/checkout@v4
|
|
166
|
-
|
|
167
|
-
- uses: actions/setup-node@v4
|
|
168
|
-
with:
|
|
169
|
-
node-version: 22
|
|
170
|
-
|
|
171
|
-
- run: npx ghost-github-portfolio sync --config config.yml --verbose
|
|
172
|
-
env:
|
|
173
|
-
GHOST_GITHUB_TOKEN: ${{ secrets.GHOST_GITHUB_TOKEN }}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Store `config.yml` in the repo (without secrets) and use environment variables for the API keys.
|
|
177
|
-
|
|
178
|
-
## How it works
|
|
179
|
-
|
|
180
|
-
1. **Fetches** all public repos for the configured GitHub user via the REST API
|
|
181
|
-
2. **Filters** by minimum stars, excludes forks and blocklisted repos
|
|
182
|
-
3. **Sorts** by star count (descending)
|
|
183
|
-
4. **Detects** banner images by checking common paths (`docs/images/banner.svg`, `media/banner.svg`, etc.)
|
|
184
|
-
5. **Generates** HTML cards with: banner, name, dynamic badges, description, tech stack
|
|
185
|
-
6. **Composes** a Ghost lexical JSON document (the format Ghost's editor uses internally)
|
|
186
|
-
7. **Updates** the target Ghost page via the Admin API with JWT authentication
|
|
187
|
-
|
|
188
|
-
Badges are dynamic (served by shields.io) — stars, forks, and Docker pulls update automatically on every page view without re-running the tool.
|
|
189
|
-
|
|
190
|
-
## Badge types
|
|
38
|
+
Open `preview.html` in a browser; Ghost is not touched. When the preview looks right, drop `--html-out` and set `GHOST_ADMIN_API_KEY` (Ghost, Settings, Integrations, "Add custom integration") to write the page. [Usage](https://github.com/GeiserX/ghost-github-portfolio/blob/main/docs/usage.md) has the other install paths.
|
|
191
39
|
|
|
192
|
-
|
|
193
|
-
|------|--------------|-------------|
|
|
194
|
-
| Stars | Yes | Always shown |
|
|
195
|
-
| Forks | Yes | Always shown |
|
|
196
|
-
| License | Yes | From GitHub repo metadata |
|
|
197
|
-
| Docker pulls | Config | Set `dockerImage` in repo overrides |
|
|
198
|
-
| Website | Yes | From GitHub homepage field |
|
|
199
|
-
| Awesome list | Yes | If repo has `awesome-list` topic |
|
|
200
|
-
| Docs | Yes | If homepage is a GitHub Pages URL |
|
|
201
|
-
| Platform | Config | Custom platform badge (macOS, Linux, etc.) |
|
|
202
|
-
| Custom | Config | Any shields.io-compatible badge |
|
|
40
|
+
## Documentation
|
|
203
41
|
|
|
204
|
-
|
|
42
|
+
- [Configuration](https://github.com/GeiserX/ghost-github-portfolio/blob/main/docs/configuration.md): `config.yml`, filters, environment variables, descriptions, banners, writing through the Ghost origin
|
|
43
|
+
- [Usage](https://github.com/GeiserX/ghost-github-portfolio/blob/main/docs/usage.md): install, init, preview and sync
|
|
44
|
+
- [How it works](https://github.com/GeiserX/ghost-github-portfolio/blob/main/docs/how-it-works.md): the page layout and the sync steps
|
|
45
|
+
- [Running on a schedule](https://github.com/GeiserX/ghost-github-portfolio/blob/main/docs/scheduling.md): the GitHub Action, cron and a Docker loop
|
|
205
46
|
|
|
206
|
-
|
|
47
|
+
The roadmap is in [docs/ROADMAP.md](https://github.com/GeiserX/ghost-github-portfolio/blob/main/docs/ROADMAP.md).
|
|
207
48
|
|
|
208
49
|
## License
|
|
209
50
|
|
|
210
|
-
[GPL-3.0](LICENSE)
|
|
51
|
+
[GPL-3.0-or-later](https://github.com/GeiserX/ghost-github-portfolio/blob/main/LICENSE)
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Config } from "./types.js";
|
|
2
|
+
export declare const DEFAULT_INTRO = "I build things nobody asked for, one repo at a time. These are the ones that got the most stars on GitHub, with a line on what each one does.";
|
|
3
|
+
export declare const DEFAULT_EXCLUDE_PATTERNS: string[];
|
|
2
4
|
export declare function loadConfig(path: string): Config;
|
|
3
5
|
export declare function generateExampleConfig(): string;
|
|
4
6
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAmB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAmB,MAAM,YAAY,CAAC;AAE1D,eAAO,MAAM,aAAa,kJACuH,CAAC;AAElJ,eAAO,MAAM,wBAAwB,UAAiB,CAAC;AAqBvD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA8E/C;AAgBD,wBAAgB,qBAAqB,IAAI,MAAM,CA2C9C"}
|
package/dist/config.js
CHANGED
|
@@ -1,118 +1,146 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { parse } from "yaml";
|
|
3
|
+
export const DEFAULT_INTRO = "I build things nobody asked for, one repo at a time. These are the ones that got the most stars on GitHub, with a line on what each one does.";
|
|
4
|
+
export const DEFAULT_EXCLUDE_PATTERNS = ["^homebrew-"];
|
|
3
5
|
const DEFAULT_PORTFOLIO = {
|
|
4
6
|
minStars: 2,
|
|
5
|
-
maxRepos:
|
|
7
|
+
maxRepos: 20,
|
|
8
|
+
columns: 3,
|
|
6
9
|
excludeRepos: [],
|
|
7
10
|
includeForked: false,
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
includeArchived: false,
|
|
12
|
+
excludeAwesomeLists: true,
|
|
10
13
|
showBanner: true,
|
|
11
|
-
centerContent: true,
|
|
12
14
|
defaultBannerPath: "docs/images/banner.svg",
|
|
13
15
|
bannerPaths: {},
|
|
16
|
+
intro: DEFAULT_INTRO,
|
|
14
17
|
repos: {},
|
|
15
|
-
footer: { showStats: true, showViewAll: true },
|
|
16
18
|
};
|
|
19
|
+
function trimSlash(url) {
|
|
20
|
+
return url.replace(/\/+$/, "");
|
|
21
|
+
}
|
|
17
22
|
export function loadConfig(path) {
|
|
18
23
|
const raw = readFileSync(path, "utf-8");
|
|
19
|
-
const parsed = parse(raw);
|
|
20
|
-
|
|
24
|
+
const parsed = (parse(raw) ?? {});
|
|
25
|
+
const github = (parsed.github ?? {});
|
|
26
|
+
const ghost = (parsed.ghost ?? {});
|
|
27
|
+
if (!github.username) {
|
|
21
28
|
throw new Error("Config: github.username is required");
|
|
22
29
|
}
|
|
23
|
-
if (!
|
|
30
|
+
if (!ghost.url) {
|
|
24
31
|
throw new Error("Config: ghost.url is required");
|
|
25
32
|
}
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
// The environment wins over the config file, so the key can stay out of it.
|
|
34
|
+
const ghostKey = process.env.GHOST_ADMIN_API_KEY || ghost.adminApiKey;
|
|
35
|
+
if (!ghostKey) {
|
|
36
|
+
throw new Error("Config: ghost.adminApiKey is required (or set GHOST_ADMIN_API_KEY)");
|
|
28
37
|
}
|
|
29
|
-
const ghost = parsed.ghost;
|
|
30
38
|
if (!ghost.pageId && !ghost.pageSlug) {
|
|
31
39
|
throw new Error("Config: ghost.pageId or ghost.pageSlug is required");
|
|
32
40
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Env var overrides (GHOST_GITHUB_TOKEN only — GITHUB_TOKEN is not used to
|
|
36
|
-
// avoid picking up unrelated tokens from gh CLI or CI environments)
|
|
41
|
+
// GHOST_GITHUB_TOKEN only. GITHUB_TOKEN is not used, to avoid picking up
|
|
42
|
+
// unrelated tokens from the gh CLI or CI environments.
|
|
37
43
|
const ghToken = github.token ?? process.env.GHOST_GITHUB_TOKEN;
|
|
38
|
-
const
|
|
44
|
+
const excludePatterns = (github.excludePatterns ??
|
|
45
|
+
DEFAULT_EXCLUDE_PATTERNS);
|
|
46
|
+
if (!Array.isArray(excludePatterns)) {
|
|
47
|
+
throw new Error("Config: github.excludePatterns must be a list");
|
|
48
|
+
}
|
|
49
|
+
for (const pattern of excludePatterns) {
|
|
50
|
+
try {
|
|
51
|
+
new RegExp(String(pattern), "i");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Config: github.excludePatterns has an invalid pattern: ${pattern}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const portfolio = (parsed.portfolio ?? {});
|
|
58
|
+
const merged = {
|
|
59
|
+
...DEFAULT_PORTFOLIO,
|
|
60
|
+
...portfolio,
|
|
61
|
+
bannerPaths: { ...portfolio.bannerPaths },
|
|
62
|
+
repos: { ...portfolio.repos },
|
|
63
|
+
excludeRepos: portfolio.excludeRepos ?? [],
|
|
64
|
+
};
|
|
65
|
+
if (!Number.isInteger(merged.columns) || merged.columns < 1 || merged.columns > 6) {
|
|
66
|
+
throw new Error("Config: portfolio.columns must be a whole number from 1 to 6");
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isInteger(merged.maxRepos) || merged.maxRepos < 1) {
|
|
69
|
+
throw new Error("Config: portfolio.maxRepos must be a whole number above 0");
|
|
70
|
+
}
|
|
39
71
|
return {
|
|
40
72
|
github: {
|
|
41
73
|
username: github.username,
|
|
42
74
|
token: ghToken,
|
|
75
|
+
excludePatterns: excludePatterns.map(String),
|
|
43
76
|
},
|
|
44
77
|
ghost: {
|
|
45
|
-
url: ghost.url
|
|
78
|
+
url: trimSlash(ghost.url),
|
|
46
79
|
adminApiKey: ghostKey,
|
|
47
80
|
pageId: ghost.pageId,
|
|
48
81
|
pageSlug: ghost.pageSlug,
|
|
82
|
+
originUrl: ghost.originUrl ? validateOriginUrl(ghost.originUrl) : undefined,
|
|
83
|
+
hostHeader: ghost.hostHeader,
|
|
49
84
|
},
|
|
50
|
-
portfolio:
|
|
51
|
-
...DEFAULT_PORTFOLIO,
|
|
52
|
-
...portfolio,
|
|
53
|
-
footer: { ...DEFAULT_PORTFOLIO.footer, ...portfolio.footer },
|
|
54
|
-
bannerPaths: {
|
|
55
|
-
...DEFAULT_PORTFOLIO.bannerPaths,
|
|
56
|
-
...portfolio.bannerPaths,
|
|
57
|
-
},
|
|
58
|
-
repos: { ...DEFAULT_PORTFOLIO.repos, ...portfolio.repos },
|
|
59
|
-
},
|
|
85
|
+
portfolio: merged,
|
|
60
86
|
};
|
|
61
87
|
}
|
|
88
|
+
/** The origin route carries the Admin key in a JWT, so the scheme must be plain http: or https:. */
|
|
89
|
+
function validateOriginUrl(value) {
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = new URL(value);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error("Config: ghost.originUrl must be a full URL such as http://ghost:2368");
|
|
96
|
+
}
|
|
97
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
98
|
+
throw new Error("Config: ghost.originUrl must start with http:// or https://");
|
|
99
|
+
}
|
|
100
|
+
return trimSlash(value);
|
|
101
|
+
}
|
|
62
102
|
export function generateExampleConfig() {
|
|
63
103
|
return `# ghost-github-portfolio configuration
|
|
64
104
|
github:
|
|
65
105
|
username: YOUR_GITHUB_USERNAME
|
|
66
|
-
# token: ghp_xxx
|
|
106
|
+
# token: ghp_xxx # Optional, for higher rate limits (env: GHOST_GITHUB_TOKEN)
|
|
107
|
+
excludePatterns: # Regular expressions matched against repo names
|
|
108
|
+
- "^homebrew-"
|
|
67
109
|
|
|
68
110
|
ghost:
|
|
69
111
|
url: https://your-ghost-blog.com
|
|
70
|
-
adminApiKey: "KEY_ID:SECRET_HEX" #
|
|
71
|
-
pageSlug: portfolio #
|
|
112
|
+
adminApiKey: "KEY_ID:SECRET_HEX" # Ghost Admin > Integrations (env GHOST_ADMIN_API_KEY wins)
|
|
113
|
+
pageSlug: portfolio # Or pageId with the page's hex ID
|
|
114
|
+
# originUrl: http://ghost:2368 # Send Admin API requests straight to the Ghost origin
|
|
115
|
+
# hostHeader: your-ghost-blog.com # Host header for originUrl (defaults to the host of url)
|
|
72
116
|
|
|
73
117
|
portfolio:
|
|
74
|
-
minStars: 2
|
|
75
|
-
maxRepos:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
118
|
+
minStars: 2 # Only repos with at least this many stars
|
|
119
|
+
maxRepos: 20 # How many cards to show, counted after every filter
|
|
120
|
+
columns: 3 # Cards per row on desktop (two on tablets, one on phones)
|
|
121
|
+
includeForked: false
|
|
122
|
+
includeArchived: false
|
|
123
|
+
excludeAwesomeLists: true # Skip repos named "awesome*" or tagged "awesome-list"
|
|
124
|
+
showBanner: true # Use the repo banner when it passes the checks, else a name tile
|
|
125
|
+
defaultBannerPath: docs/images/banner.svg
|
|
81
126
|
|
|
82
|
-
|
|
127
|
+
intro: "${DEFAULT_INTRO}"
|
|
83
128
|
|
|
84
|
-
#
|
|
85
|
-
bannerPaths:
|
|
86
|
-
#
|
|
129
|
+
# Banner path for specific repos, tried before defaultBannerPath
|
|
130
|
+
bannerPaths: {}
|
|
131
|
+
# my-project: media/banner.svg
|
|
87
132
|
|
|
88
133
|
excludeRepos:
|
|
89
|
-
- .github
|
|
90
|
-
# - my-private-project
|
|
134
|
+
- .github
|
|
91
135
|
|
|
92
136
|
# Per-repo overrides
|
|
93
137
|
repos: {}
|
|
94
138
|
# my-project:
|
|
95
|
-
# description: "
|
|
96
|
-
#
|
|
97
|
-
#
|
|
139
|
+
# description: "One line that replaces the GitHub description"
|
|
140
|
+
# homepage: https://example.com/guide/ # Replaces the GitHub homepage field
|
|
141
|
+
# siteLabel: Guide # Link text, defaults to "Site"
|
|
98
142
|
# bannerPath: assets/banner.png
|
|
99
|
-
#
|
|
100
|
-
# - type: website
|
|
101
|
-
# url: https://my-project.com
|
|
102
|
-
# - type: awesome-list
|
|
103
|
-
# - type: platform
|
|
104
|
-
# label: macOS
|
|
105
|
-
# logo: apple
|
|
106
|
-
# - type: docs
|
|
107
|
-
# url: https://docs.my-project.com
|
|
108
|
-
# - type: custom
|
|
109
|
-
# label: MCP
|
|
110
|
-
# value: Official Registry
|
|
111
|
-
# color: E6522C
|
|
112
|
-
|
|
113
|
-
footer:
|
|
114
|
-
showStats: true # Show total stars and repo count
|
|
115
|
-
showViewAll: true # Show "View All Repositories" link
|
|
143
|
+
# exclude: false
|
|
116
144
|
`;
|
|
117
145
|
}
|
|
118
146
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAG7B,MAAM,iBAAiB,GAAoB;IACzC,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,EAAE;IACZ,YAAY,EAAE,EAAE;IAChB,aAAa,EAAE,KAAK;IACpB,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/generator.d.ts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import type { Config, GitHubRepo, LexicalDocument } from "./types.js";
|
|
2
|
+
export declare const MAX_DESCRIPTION_LENGTH = 140;
|
|
3
|
+
/**
|
|
4
|
+
* Turn a GitHub description into one short line: emoji and :shortcodes:
|
|
5
|
+
* removed, em and en dashes turned into commas, first sentence only,
|
|
6
|
+
* at most 140 characters.
|
|
7
|
+
*/
|
|
8
|
+
export declare function cleanDescription(text: string, max?: number): string;
|
|
9
|
+
export declare function formatStars(count: number): string;
|
|
10
|
+
export interface SiteLink {
|
|
11
|
+
url: string;
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
/** The card's site link: the override, else the GitHub homepage unless it points at github.com. */
|
|
15
|
+
export declare function siteLink(repo: GitHubRepo, config: Config): SiteLink | null;
|
|
16
|
+
export declare function repoDescription(repo: GitHubRepo, config: Config): string;
|
|
2
17
|
export declare function generateCard(repo: GitHubRepo, bannerUrl: string | null, config: Config): string;
|
|
3
|
-
export
|
|
4
|
-
|
|
18
|
+
export interface PortfolioInput {
|
|
19
|
+
/** Card HTML from generateCard, in display order. */
|
|
20
|
+
cards: string[];
|
|
21
|
+
/** Number of public repositories the user owns, used for the closing line. */
|
|
22
|
+
totalRepos: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The whole page as the HTML of one Ghost html card. The `kg-width-wide`
|
|
26
|
+
* class lets themes built on Ghost's content grid (Alto, Source, Casper)
|
|
27
|
+
* give the cards the wide column; the text stays at reading width.
|
|
28
|
+
*/
|
|
29
|
+
export declare function generatePortfolioHtml(input: PortfolioInput, config: Config): string;
|
|
30
|
+
export declare function buildLexical(html: string): LexicalDocument;
|
|
31
|
+
/** The html card as Ghost renders it, with its begin and end markers. */
|
|
32
|
+
export declare function wrapHtmlCard(html: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* A standalone dark page that approximates Ghost's Alto theme in dark mode
|
|
35
|
+
* (content grid, widths, the .gh-content rules that reach into an html
|
|
36
|
+
* card), for checking the output in a browser before syncing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildPreviewPage(html: string, title?: string): string;
|
|
5
39
|
//# sourceMappingURL=generator.d.ts.map
|
package/dist/generator.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAK1C;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,GAAG,SAAyB,GAC3B,MAAM,CA4BR;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mGAAmG;AACnG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAgB1E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIxE;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,GACb,MAAM,CA0BR;AAsCD,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,GACb,MAAM,CAiBR;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAW1D;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAc,GAAG,MAAM,CAwC1E"}
|