This is an automated email from the ASF dual-hosted git repository.

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon-website.git


The following commit(s) were added to refs/heads/master by this push:
     new 3d3eaae5 feat: download github avatars (#28)
3d3eaae5 is described below

commit 3d3eaae5cd9eeef609c655f0b730d62cf9f7c170
Author: Laffery <[email protected]>
AuthorDate: Fri Jan 16 09:59:58 2026 +0800

    feat: download github avatars (#28)
---
 .gitignore                                         |   1 +
 README.md                                          |   2 +
 package.json                                       |   4 +-
 pnpm-lock.yaml                                     | 303 ++++++++++++++++++++-
 .../home/components/team/team.component.html       |   2 +-
 src/app/routers/team/team.component.html           |   4 +-
 src/load-avatars.ts                                |  95 +++++++
 7 files changed, 403 insertions(+), 8 deletions(-)

diff --git a/.gitignore b/.gitignore
index 6e3ac072..eca1e73f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
 # See 
https://docs.github.com/get-started/getting-started-with-git/ignoring-files for 
more about ignoring files.
 
 # Compiled output
+/src/assets/avatars/
 /src/assets/metadata/
 /src/assets/docs/
 /src/sitemap.xml
diff --git a/README.md b/README.md
index 6721aef9..e56f6fd8 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ Run `pnpm install` to install all dependencies.
 
 Run `pnpm start` for a dev server. Navigate to `http://localhost:8801/`. The 
application will automatically reload if you change any of the source files.
 
+> Before starting the dev server, make sure you have run `pnpm avatar` to load 
the avatars of the team members.
+
 ### Build
 
 Run `pnpm build` to build the project. The build artifacts will be stored in 
the `dist/` directory.
diff --git a/package.json b/package.json
index 9b83cc2b..702d61de 100644
--- a/package.json
+++ b/package.json
@@ -2,10 +2,11 @@
   "name": "paimon-next-website",
   "version": "0.0.0",
   "scripts": {
+    "avatar": "tsx ./src/load-avatars.ts",
     "parse": "cd library/markdown-parser && npm run parse",
     "prestart": "npm run parse",
     "start": "ng serve --port 8801",
-    "prebuild": "npm run parse",
+    "prebuild": "npm run avatar && npm run parse",
     "build": "ng build",
     "serve:ssr": "node dist/server/server.mjs",
     "i18n:extract": "ngx-translate-extract --input ./src --output 
./src/assets/i18n/{en,zh}.json -ss -k --clean --format json",
@@ -60,6 +61,7 @@
     "prettier-plugin-tailwindcss": "^0.6.6",
     "tailwindcss": "^3.4.10",
     "tailwindcss-animation-delay": "^2.0.1",
+    "tsx": "^4.21.0",
     "typescript": "~5.5.2",
     "typescript-eslint": "8.2.0"
   },
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 87bf102a..de756f7d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,7 +62,7 @@ importers:
     devDependencies:
       '@angular-devkit/build-angular':
         specifier: ^18.2.2
-        version: 18.2.6(5ez4bdpgzwqrz4i7y6zlfrwqqy)
+        version: 18.2.6(af889e1f26053a970339fafa7663b129)
       '@angular/cli':
         specifier: ^18.2.2
         version: 18.2.6([email protected])
@@ -138,6 +138,9 @@ importers:
       tailwindcss-animation-delay:
         specifier: ^2.0.1
         version: 
2.0.1([email protected]([email protected](@types/[email protected])([email protected])))
+      tsx:
+        specifier: ^4.21.0
+        version: 4.21.0
       typescript:
         specifier: ~5.5.2
         version: 5.5.4
@@ -1152,6 +1155,12 @@ packages:
     cpu: [ppc64]
     os: [aix]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
     engines: {node: '>=12'}
@@ -1164,6 +1173,12 @@ packages:
     cpu: [arm64]
     os: [android]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
     engines: {node: '>=12'}
@@ -1176,6 +1191,12 @@ packages:
     cpu: [arm]
     os: [android]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
     engines: {node: '>=12'}
@@ -1188,6 +1209,12 @@ packages:
     cpu: [x64]
     os: [android]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
     engines: {node: '>=12'}
@@ -1200,6 +1227,12 @@ packages:
     cpu: [arm64]
     os: [darwin]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
     engines: {node: '>=12'}
@@ -1212,6 +1245,12 @@ packages:
     cpu: [x64]
     os: [darwin]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
     engines: {node: '>=12'}
@@ -1224,6 +1263,12 @@ packages:
     cpu: [arm64]
     os: [freebsd]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
     engines: {node: '>=12'}
@@ -1236,6 +1281,12 @@ packages:
     cpu: [x64]
     os: [freebsd]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
     engines: {node: '>=12'}
@@ -1248,6 +1299,12 @@ packages:
     cpu: [arm64]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
     engines: {node: '>=12'}
@@ -1260,6 +1317,12 @@ packages:
     cpu: [arm]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
     engines: {node: '>=12'}
@@ -1272,6 +1335,12 @@ packages:
     cpu: [ia32]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
     engines: {node: '>=12'}
@@ -1284,6 +1353,12 @@ packages:
     cpu: [loong64]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
     engines: {node: '>=12'}
@@ -1296,6 +1371,12 @@ packages:
     cpu: [mips64el]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
     engines: {node: '>=12'}
@@ -1308,6 +1389,12 @@ packages:
     cpu: [ppc64]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
     engines: {node: '>=12'}
@@ -1320,6 +1407,12 @@ packages:
     cpu: [riscv64]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
     engines: {node: '>=12'}
@@ -1332,6 +1425,12 @@ packages:
     cpu: [s390x]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
     engines: {node: '>=12'}
@@ -1344,6 +1443,18 @@ packages:
     cpu: [x64]
     os: [linux]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [netbsd]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
     engines: {node: '>=12'}
@@ -1356,12 +1467,24 @@ packages:
     cpu: [x64]
     os: [netbsd]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [openbsd]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
     engines: {node: '>=12'}
@@ -1374,6 +1497,18 @@ packages:
     cpu: [x64]
     os: [openbsd]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openharmony]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
     engines: {node: '>=12'}
@@ -1386,6 +1521,12 @@ packages:
     cpu: [x64]
     os: [sunos]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
     engines: {node: '>=12'}
@@ -1398,6 +1539,12 @@ packages:
     cpu: [arm64]
     os: [win32]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
     engines: {node: '>=12'}
@@ -1410,6 +1557,12 @@ packages:
     cpu: [ia32]
     os: [win32]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
   '@esbuild/[email protected]':
     resolution: {integrity: 
sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
     engines: {node: '>=12'}
@@ -1422,6 +1575,12 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@esbuild/[email protected]':
+    resolution: {integrity: 
sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
   '@eslint-community/[email protected]':
     resolution: {integrity: 
sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3001,6 +3160,11 @@ packages:
     engines: {node: '>=18'}
     hasBin: true
 
+  [email protected]:
+    resolution: {integrity: 
sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==}
+    engines: {node: '>=18'}
+    hasBin: true
+
   [email protected]:
     resolution: {integrity: 
sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
     engines: {node: '>=6'}
@@ -3340,6 +3504,9 @@ packages:
     resolution: {integrity: 
sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
     engines: {node: '>= 0.4'}
 
+  [email protected]:
+    resolution: {integrity: 
sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
+
   [email protected]:
     resolution: {integrity: 
sha512-aMgPyjC9W5Mz9tbFU8DcQ7GYMXoFWq633kaWGt4imlcpBWzDIWk7HY7nCSZTCJxyjRaLq9L/NEjMKkZ9gR630Q==}
 
@@ -4769,6 +4936,9 @@ packages:
     resolution: {integrity: 
sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
     engines: {node: '>=8'}
 
+  [email protected]:
+    resolution: {integrity: 
sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
   [email protected]:
     resolution: {integrity: 
sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==}
     engines: {node: '>=12'}
@@ -5299,6 +5469,11 @@ packages:
   [email protected]:
     resolution: {integrity: 
sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
 
+  [email protected]:
+    resolution: {integrity: 
sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
+    engines: {node: '>=18.0.0'}
+    hasBin: true
+
   [email protected]:
     resolution: {integrity: 
sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==}
     engines: {node: ^16.14.0 || >=18.0.0}
@@ -5680,13 +5855,13 @@ snapshots:
     transitivePeerDependencies:
       - chokidar
 
-  '@angular-devkit/[email protected](5ez4bdpgzwqrz4i7y6zlfrwqqy)':
+  '@angular-devkit/[email protected](af889e1f26053a970339fafa7663b129)':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@angular-devkit/architect': 0.1802.6([email protected])
       '@angular-devkit/build-webpack': 
0.1802.6([email protected])([email protected]([email protected]))([email protected]([email protected]))
       '@angular-devkit/core': 18.2.6([email protected])
-      '@angular/build': 18.2.6(stdzbpsd2y76kkjxukjo2bjzki)
+      '@angular/build': 18.2.6(38d3034695d993a4a3411ce8e26edddb)
       '@angular/compiler-cli': 
18.2.6(@angular/[email protected](@angular/[email protected]([email protected])([email protected])))([email protected])
       '@babel/core': 7.25.2
       '@babel/generator': 7.25.0
@@ -5858,7 +6033,7 @@ snapshots:
       '@angular/core': 18.2.6([email protected])([email protected])
       tslib: 2.7.0
 
-  '@angular/[email protected](stdzbpsd2y76kkjxukjo2bjzki)':
+  '@angular/[email protected](38d3034695d993a4a3411ce8e26edddb)':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@angular-devkit/architect': 0.1802.6([email protected])
@@ -6942,141 +7117,219 @@ snapshots:
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@esbuild/[email protected]':
     optional: true
 
   '@esbuild/[email protected]':
     optional: true
 
+  '@esbuild/[email protected]':
+    optional: true
+
   '@eslint-community/[email protected]([email protected]([email protected]))':
     dependencies:
       eslint: 9.11.1([email protected])
@@ -8965,6 +9218,35 @@ snapshots:
       '@esbuild/win32-ia32': 0.23.0
       '@esbuild/win32-x64': 0.23.0
 
+  [email protected]:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.27.2
+      '@esbuild/android-arm': 0.27.2
+      '@esbuild/android-arm64': 0.27.2
+      '@esbuild/android-x64': 0.27.2
+      '@esbuild/darwin-arm64': 0.27.2
+      '@esbuild/darwin-x64': 0.27.2
+      '@esbuild/freebsd-arm64': 0.27.2
+      '@esbuild/freebsd-x64': 0.27.2
+      '@esbuild/linux-arm': 0.27.2
+      '@esbuild/linux-arm64': 0.27.2
+      '@esbuild/linux-ia32': 0.27.2
+      '@esbuild/linux-loong64': 0.27.2
+      '@esbuild/linux-mips64el': 0.27.2
+      '@esbuild/linux-ppc64': 0.27.2
+      '@esbuild/linux-riscv64': 0.27.2
+      '@esbuild/linux-s390x': 0.27.2
+      '@esbuild/linux-x64': 0.27.2
+      '@esbuild/netbsd-arm64': 0.27.2
+      '@esbuild/netbsd-x64': 0.27.2
+      '@esbuild/openbsd-arm64': 0.27.2
+      '@esbuild/openbsd-x64': 0.27.2
+      '@esbuild/openharmony-arm64': 0.27.2
+      '@esbuild/sunos-x64': 0.27.2
+      '@esbuild/win32-arm64': 0.27.2
+      '@esbuild/win32-ia32': 0.27.2
+      '@esbuild/win32-x64': 0.27.2
+
   [email protected]: {}
 
   [email protected]: {}
@@ -9385,6 +9667,10 @@ snapshots:
       es-errors: 1.3.0
       get-intrinsic: 1.2.4
 
+  [email protected]:
+    dependencies:
+      resolve-pkg-maps: 1.0.0
+
   [email protected]:
     dependencies:
       content-type: 1.0.5
@@ -10785,6 +11071,8 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]: {}
+
   [email protected]:
     dependencies:
       adjust-sourcemap-loader: 4.0.0
@@ -11437,6 +11725,13 @@ snapshots:
 
   [email protected]: {}
 
+  [email protected]:
+    dependencies:
+      esbuild: 0.27.2
+      get-tsconfig: 4.13.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
   [email protected]:
     dependencies:
       '@tufjs/models': 2.0.1
diff --git a/src/app/routers/home/components/team/team.component.html 
b/src/app/routers/home/components/team/team.component.html
index 558c73c6..df20e17d 100644
--- a/src/app/routers/home/components/team/team.component.html
+++ b/src/app/routers/home/components/team/team.component.html
@@ -31,7 +31,7 @@
             <div class="h-full w-full rounded bg-paimon-gray-2">
               <img
                 class="rounded"
-                [ngSrc]="'https://avatars.githubusercontent.com/u/' + 
item.avatarId"
+                ngSrc="assets/avatars/{{ item.avatarId }}.png"
                 [width]="92"
                 [height]="92"
                 [alt]="item.name"
diff --git a/src/app/routers/team/team.component.html 
b/src/app/routers/team/team.component.html
index 856c5e45..b7a86c71 100644
--- a/src/app/routers/team/team.component.html
+++ b/src/app/routers/team/team.component.html
@@ -13,7 +13,7 @@
                 class="flex w-[156px] flex-col items-center justify-between 
gap-4 rounded-lg bg-paimon-gray-1 p-1.5 pb-6"
               >
                 <img
-                  [ngSrc]="'https://avatars.githubusercontent.com/u/' + 
item.avatarId"
+                  ngSrc="assets/avatars/{{ item.avatarId }}.png"
                   [width]="144"
                   [height]="144"
                   [alt]="item.name"
@@ -47,7 +47,7 @@
                 class="flex w-[156px] flex-col items-center justify-between 
gap-4 rounded-lg bg-paimon-gray-1 p-1.5 pb-6"
               >
                 <img
-                  [ngSrc]="'https://avatars.githubusercontent.com/u/' + 
item.avatarId"
+                  ngSrc="assets/avatars/{{ item.avatarId }}.png"
                   [width]="144"
                   [height]="144"
                   [alt]="item.name"
diff --git a/src/load-avatars.ts b/src/load-avatars.ts
new file mode 100644
index 00000000..2e836e75
--- /dev/null
+++ b/src/load-avatars.ts
@@ -0,0 +1,95 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import fs from 'node:fs';
+import { get } from 'node:https';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { listOfPMCs, listOfCommitters } from './app/tokens/member';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const OUTPUT_DIR = path.resolve(__dirname, 'assets', 'avatars');
+const AVATAR_SIZE = 256;
+
+const avatars = new Set<string>([
+  ...listOfPMCs.map(member => member.avatarId),
+  ...listOfCommitters.map(member => member.avatarId)
+]);
+
+function fetchBuffer(url: string): Promise<Buffer> {
+  return new Promise((resolve, reject) => {
+    get(
+      url,
+      {
+        headers: {
+          // GitHub requires a user agent for avatar fetches.
+          'User-Agent': 'paimon-website-avatar-downloader'
+        }
+      },
+      res => {
+        if (res.statusCode && res.statusCode >= 400) {
+          return reject(new Error(`Failed to fetch ${url}: 
${res.statusCode}`));
+        }
+
+        const data: Uint8Array[] = [];
+        res.on('data', chunk => data.push(chunk));
+        res.on('end', () => resolve(Buffer.concat(data)));
+      }
+    ).on('error', err => reject(err));
+  });
+}
+
+async function downloadAvatar(avatarId: string): Promise<void> {
+  const url = 
`https://avatars.githubusercontent.com/u/${avatarId}?s=${AVATAR_SIZE}`;
+  const targetPath = path.join(OUTPUT_DIR, `${avatarId}.png`);
+
+  if (fs.existsSync(targetPath)) {
+    return;
+  }
+
+  const buffer = await fetchBuffer(url);
+  await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
+  await fs.promises.writeFile(targetPath, buffer);
+}
+
+// Download avatars from GitHub if not already present, and save them to 
/src/assets/avatars/
+export async function loadAvatars(): Promise<void> {
+  const ids = Array.from(avatars.values());
+
+  const results = await Promise.allSettled(ids.map(id => downloadAvatar(id)));
+  const failures = results
+    .map((result, index) => ({ result, id: ids[index] }))
+    .filter(({ result }) => result.status === 'rejected');
+
+  if (failures.length > 0) {
+    const message = failures.map(({ id, result }) => `${id}: ${(result as 
PromiseRejectedResult).reason}`).join('\n');
+    throw new Error(`Failed to download ${failures.length} 
avatar(s):\n${message}`);
+  }
+}
+
+if (process.argv[1] === __filename) {
+  loadAvatars()
+    .then(() => console.log('Avatar download complete.'))
+    .catch(err => {
+      console.error(err);
+      process.exitCode = 1;
+    });
+}

Reply via email to