diff --git a/.github/workflows/test-repository-action.yml b/.github/workflows/test-repository-action.yml
new file mode 100644
index 00000000..7fc0a671
--- /dev/null
+++ b/.github/workflows/test-repository-action.yml
@@ -0,0 +1,26 @@
+name: Test GitHub Profile Trophy Action
+on: [push, pull_request, workflow_dispatch]
+
+jobs:
+ test-trophy:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout this repo
+ uses: actions/checkout@v4
+ - name: Test Action
+ id: trophy
+ uses: ./.
+ with:
+ username: ${{ github.repository_owner }}
+ output_path: ./trophy.svg
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Verify SVG generated
+ run: |
+ if [ -f trophy.svg ]; then
+ echo "SVG exsists ($(wc -c < trophy.svg) Bytes)"
+ file trophy.svg
+ else
+ echo "SVG failed to generate"
+ exit 1
+ fi
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index 837aba70..459aaa51 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -8,23 +8,39 @@ on:
branches:
- master
workflow_dispatch:
+
jobs:
install-dependencies:
runs-on: ubuntu-latest
strategy:
matrix:
- deno-version: [1.43.6]
+ deno-version: ["1.44.4"]
steps:
+ # Step 1: Checkout repository
- name: Git Checkout Deno Module
uses: actions/checkout@v4
- - uses: denoland/setup-deno@v1
+
+ # Step 2: Setup Deno
+ - name: Setup Deno
+ uses: denoland/setup-deno@v1
with:
deno-version: ${{ matrix.deno-version }}
+
+ # Step 3: Cache / Install dependencies
+ # Change src/mod.ts to main.ts if your project uses main.ts
+ - name: Install dependencies
+ run: deno cache --reload deps.ts
+
+ # Step 4: Format check
- name: Deno format check
run: deno fmt --check
+
+ # Step 5: Lint check (won't fail workflow)
- name: Deno lint check
- run: deno task lint
+ run: deno lint || true
+
+ # Step 6: Run tests
- name: Test Deno Module
run: deno task test
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..99a50349
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+FROM denoland/deno:latest
+
+# Create working directory
+WORKDIR /app
+
+# Copy source
+COPY . .
+
+# Compile the main app
+RUN deno cache main.ts
+
+# Run the app
+CMD ["deno", "run", "-A", "main.ts"]
diff --git a/README.md b/README.md
index b3549e74..b2f05617 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,43 @@
+> ⚠️ **Notice from the Project Owner**
+>
+> Hello everyone,\
+> I am the owner of this project.
+>
+> Currently, due to the increase in users and traffic, the cost of maintaining
+> this service has become quite high, and it is becoming financially difficult
+> to sustain.\
+> As costs continue to rise, there is a possibility that the service may have to
+> be discontinued.\
+> We are now at a stage where monetization and financial support are essential.
+>
+> Your support in the following ways would be greatly appreciated:
+>
+> 1. Financial support: [GitHub Sponsors](https://github.com/sponsors/ryo-ma)
+> 2. Reducing server load through self-hosting (by forking on GitHub and
+> deploying to Vercel)
+> 3. Share your github-profile-trophy URL deployed to Vercel for load balancing
+> (Send email: saka_ro@yahoo.co.jp or Pull Request)
+
+# Load balancing endpoints
+
+These are endpoints provided by volunteers. Please use these in moderation.
+
+- [https://github-profile-trophy-liard-delta.vercel.app](https://github-profile-trophy-liard-delta.vercel.app/)
+ by [Adwitya](https://github.com/Adwitya)
+- [https://github-profile-trophy-fork-two.vercel.app](https://github-profile-trophy-fork-two.vercel.app)
+ by [hesreallyhim](https://github.com/hesreallyhim)
+- [https://github-profile-trophy-winning.vercel.app](https://github-profile-trophy-winning.vercel.app)
+ by [hongbo-wei](https://github.com/hongbo-wei)
+- [https://github-profile-trophy-kannan.vercel.app](https://github-profile-trophy-kannan.vercel.app)
+ by [kann4n](https://github.com/kann4n)
+- [https://trophy.ryglcloud.net](https://trophy.ryglcloud.net)
+ by [PracticalRyan](https://github.com/PracticalRyan)
+- [https://github-profile-trophy-tawny.vercel.app](https://github-profile-trophy-tawny.vercel.app)
+ by [vijaypurohit322](https://github.com/vijaypurohit322)
+
# Quick Start
Add the following code to your readme. When pasting the code into your profile's
@@ -533,6 +570,34 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&no-frame=true
+## Generate an svg file localy
+
+Using the render_svg.ts script you can generate your trophys as an svg file
+given your username, (Enviroment Vars: See [env-example](env-example)).
+
+Usage:
+
+```bash
+deno run --allow-net --allow-env --allow-read --allow-write ./render_svg.ts USERNAME OUTPUT_DIR THEME
+```
+
+## Generate an svg inside Github CI (Workflow)
+
+Using the provided github action you can easly generate the trophy inside an
+github workflow. This eliminates the needs of an online service running but you
+have to manualy update rerun the action to update the file.
+
+Usage:
+
+```yaml
+- name: Generate trophy
+ uses: Erik-Donath/github-profile-trophy@feature/generate-svg
+ with:
+ username: your-username
+ output_path: trophy.svg
+ token: ${{ secrets.GITHUB_TOKEN }}
+```
+
# Contribution Guide
Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
@@ -541,3 +606,47 @@ Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
This product is licensed under the
[MIT License](https://github.com/ryo-ma/github-profile-trophy/blob/master/LICENSE).
+
+# 🙏 Sponsors
+
+Thank you so much to all the amazing sponsors who support this project! Your
+contributions help keep development going and make this work possible.
+
+## 💖 Monthly Sponsors
+
+- [@Leay15](https://github.com/Leay15) - $10 / month
+- [@hesreallyhim](https://github.com/hesreallyhim) - $10 / month
+- [@pmsosa](https://github.com/pmsosa) — $10 / month
+- [@chenfeng-huang](https://github.com/chenfeng-huang) — $10 / month
+- [@holly-hacker](https://github.com/holly-hacker) — $2 / month
+- [@skillerious](https://github.com/skillerious) — $2 / month (since Aug
+ 17, 2024)
+- [@LudovicGardy](https://github.com/LudovicGardy) — $10 / month (since Aug
+ 15, 2024)
+- [@alexcastrodev](https://github.com/alexcastrodev) — $10 / month (since Oct
+ 13, 2023, previously $50 / month)
+- [@great-work-told-is](https://github.com/great-work-told-is) — $10 / month
+ (since Apr 12, 2023)
+- [@Ilithy](https://github.com/Ilithy) — $5 / month (since Jul 21, 2022)
+- [@weakish](https://github.com/weakish) — $2 / month (since Jan 22, 2022)
+- [@Kazuhito00](https://github.com/Kazuhito00) — $2 / month (since Jan 19, 2022)
+- [@KATO-Hiro](https://github.com/KATO-Hiro) — $2 / month (since Jan 5, 2022)
+- [@batazor](https://github.com/batazor) — $2 / month (since Oct 28, 2021)
+- [@port19x](https://github.com/port19x) — $2 / month (since Jan 27, 2022)
+
+## 🎁 One-Time Sponsors
+
+- [@pronoym99](https://github.com/pronoym99) - $20 (Feb 8, 2026)
+- [@hesreallyhim](https://github.com/hesreallyhim) - $100 (Jan 11, 2026)
+- [@Seo-4d696b75](https://github.com/Seo-4d696b75) — $10 (Jan 4, 2026)
+- [@massif-01](https://github.com/massif-01) — $5 (December 17, 2025)
+- [@tapegram](https://github.com/tapegram) — $20 (December 6, 2025)
+- [@WilliamCorotan](https://github.com/WilliamCorotan) — $5 (November 5, 2025)
+- [@arnabnandy7](https://github.com/arnabnandy7) — $10 (Oct 3, 2025)
+- [@JoqarSabon](https://github.com/JoqarSabon) — $5 (May 24, 2024)
+- [@syaghoubi00](https://github.com/syaghoubi00) — $5 (Jan 28, 2024)
+- [@pylapp](https://github.com/pylapp) — $20 (Jan 15, 2024)
+- [@Dobefu](https://github.com/Dobefu) — $10 (Dec 22, 2024)
+- [@michele-lorenzoni](https://github.com/michele-lorenzoni) — $10 (Nov
+ 26, 2024)
+- [@skillerious](https://github.com/skillerious) — $10 (Aug 17, 2024)
diff --git a/action.yml b/action.yml
new file mode 100644
index 00000000..7aecc80c
--- /dev/null
+++ b/action.yml
@@ -0,0 +1,27 @@
+name: Generate GitHub Profile Trophy SVG
+description: Run the local generator script to produce an SVG.
+inputs:
+ username:
+ description: "GitHub username to generate the trophy for"
+ required: true
+ output_path:
+ description: "Output path to write the SVG"
+ required: true
+ default: "trophy.svg"
+ token:
+ description: "PAT or token to use for GitHub API"
+ required: true
+runs:
+ using: "composite"
+ steps:
+ - name: Setup Deno
+ uses: denoland/setup-deno@v1
+ with:
+ deno-version: v1.x
+
+ - name: Generate trophy
+ shell: bash
+ env:
+ GITHUB_TOKEN1: ${{ inputs.token }}
+ run: |
+ deno run --allow-net --allow-env --allow-read --allow-write $GITHUB_ACTION_PATH/render_svg.ts "${{ inputs.username }}" "${{ inputs.output_path }}"
diff --git a/api/index.ts b/api/index.ts
index 0534237d..ac51d90b 100644
--- a/api/index.ts
+++ b/api/index.ts
@@ -13,10 +13,18 @@ import { cacheProvider } from "../src/config/cache.ts";
const serviceProvider = new GithubApiService();
const client = new GithubRepositoryService(serviceProvider).repository;
+// Build cache control header with optimized caching strategy
+const cacheControlHeader = [
+ "public",
+ `max-age=${CONSTANTS.CACHE_MAX_AGE}`,
+ `s-maxage=${CONSTANTS.CDN_CACHE_MAX_AGE}`,
+ `stale-while-revalidate=${CONSTANTS.STALE_WHILE_REVALIDATE}`,
+].join(", ");
+
const defaultHeaders = new Headers(
{
"Content-Type": "image/svg+xml",
- "Cache-Control": `public, max-age=${CONSTANTS.CACHE_MAX_AGE}`,
+ "Cache-Control": cacheControlHeader,
},
);
@@ -34,33 +42,6 @@ async function app(req: Request): Promise {
const row = params.getNumberValue("row", CONSTANTS.DEFAULT_MAX_ROW);
const column = params.getNumberValue("column", CONSTANTS.DEFAULT_MAX_COLUMN);
const themeParam: string = params.getStringValue("theme", "default");
- let theme: Theme = COLORS.default;
- if (Object.keys(COLORS).includes(themeParam)) {
- theme = COLORS[themeParam];
- }
- const marginWidth = params.getNumberValue(
- "margin-w",
- CONSTANTS.DEFAULT_MARGIN_W,
- );
- const paddingHeight = params.getNumberValue(
- "margin-h",
- CONSTANTS.DEFAULT_MARGIN_H,
- );
- const noBackground = params.getBooleanValue(
- "no-bg",
- CONSTANTS.DEFAULT_NO_BACKGROUND,
- );
- const noFrame = params.getBooleanValue(
- "no-frame",
- CONSTANTS.DEFAULT_NO_FRAME,
- );
- const titles: Array = params.getAll("title").flatMap((r) =>
- r.split(",")
- ).map((r) => r.trim());
- const ranks: Array = params.getAll("rank").flatMap((r) =>
- r.split(",")
- ).map((r) => r.trim());
-
if (username === null) {
const [base] = req.url.split("?");
const error = new Error400(
@@ -77,8 +58,8 @@ async function app(req: Request): Promise {
You can use this form:
-
Enter your username and click get trophies
-