Repository: felix-berlin/webshaped-blog-astro
Pull request #72: refactor stats endpoint
Author: login: felix-berlin
Labels: name: released
Created: 2025-05-10T17:35:02Z | Merged: 2025-05-10T17:39:08Z



Diff:
diff --git a/src/pages/api/github/stats.ts b/src/pages/api/github/stats.ts
index 2a92c887..7b795881 100644
--- a/src/pages/api/github/stats.ts
+++ b/src/pages/api/github/stats.ts
@@ -5,16 +5,38 @@ import GhRepos from "@services/github/GhRepos.graphql";
 import GhSingleRepo from "@services/github/GhSingleRepo.graphql";
 import { cacheExchange } from "@urql/exchange-graphcache";
 
+// --- Types ---
+type RepoNode = {
+  name: string;
+  stargazerCount: number;
+  url: string;
+  description?: string;
+  forkCount: number;
+  languages: { edges: { node: { name: string }; size: number }[] };
+  defaultBranchRef?: {
+    target?: {
+      history?: { totalCount?: number };
+    };
+  };
+};
+
+type RepoSummary = {
+  name: string;
+  stars: number;
+  url: string;
+  description: string;
+  mostUsedLanguage: string;
+  forkCount: number;
+};
+
+// --- Cache config ---
 const cache = cacheExchange({
   resolvers: {
     User: {
-      repositories: (parent, args, cache, info) => {
-        return {
-          __typename: "RepositoryConnection",
-          // Wichtig: args als key für Pagination
-          args,
-        };
-      },
+      repositories: (parent, args) => ({
+        __typename: "RepositoryConnection",
+        args,
+      }),
     },
   },
   keys: {
@@ -25,7 +47,6 @@ const cache = cacheExchange({
     Ref: () => null,
     Commit: () => null,
   },
-  // TTL-Konfiguration (1 Tag in Millisekunden)
   ttl: 24 * 60 * 60 * 1000,
 });
 
@@ -42,9 +63,37 @@ const client = new Client({
 // List of additional repos (owner/name)
 const extraRepos = [{ owner: "bitsundbaeume", name: "publication2023" }];
 
+// --- Helper: Aggregate repo data ---
+function aggregateRepoData(
+  repo: RepoNode,
+  languages: Record<string, number>,
+  mostStarredRepos: RepoSummary[],
+  totals: { bytes: number; commits: number },
+) {
+  for (const language of repo.languages.edges) {
+    const languageName = language.node.name;
+    const size = language.size;
+    if (!languages[languageName]) languages[languageName] = 0;
+    languages[languageName] += size;
+    totals.bytes += size;
+  }
+  if (repo.defaultBranchRef?.target?.history?.totalCount) {
+    totals.commits += repo.defaultBranchRef.target.history.totalCount;
+  }
+  const mostUsedLanguage = repo.languages.edges[0]?.node.name || "Unknown";
+  mostStarredRepos.push({
+    name: repo.name,
+    stars: repo.stargazerCount,
+    url: repo.url,
+    description: repo.description || "No description available",
+    mostUsedLanguage,
+    forkCount: repo.forkCount,
+  });
+}
+
 export async function GET(context: APIContext): Promise<Response> {
   try {
-    // Fetch user information
+    // --- Fetch user info ---
     const USER_QUERY = `
       query {
         viewer {
@@ -53,134 +102,75 @@ export async function GET(context: APIContext): Promise<Response> {
         }
       }
     `;
-
     const userResponse = await client.query(USER_QUERY, {}).toPromise();
-
     if (userResponse.error) {
       throw new Error(`Error fetching user info: ${userResponse.error.message}`);
     }
-
     const username = userResponse.data.viewer.login;
 
-    // Fetch repositories and aggregate data
+    // --- Aggregation setup ---
     let hasNextPage = true;
     let after: string | null = null;
-    const languages: { [key: string]: number } = {};
-    let totalBytes = 0;
-    let totalCommits = 0;
-    let mostStarredRepos: {
-      name: string;
-      stars: number;
-      url: string;
-      description: string;
-      mostUsedLanguage: string;
-      forkCount: number;
-    }[] = [];
+    const languages: Record<string, number> = {};
+    const mostStarredRepos: RepoSummary[] = [];
+    const totals = { bytes: 0, commits: 0 };
 
+    // --- Fetch all user repos (paginated) ---
     while (hasNextPage) {
       const reposResponse = await client.query(GhRepos, { username, after }).toPromise();
-
       if (reposResponse.error) {
         throw new Error(`Error fetching repositories: ${reposResponse.error.message}`);
       }
-
       const repositories = reposResponse.data.user.repositories;
       hasNextPage = repositories.pageInfo.hasNextPage;
       after = repositories.pageInfo.endCursor;
 
-      for (const repo of repositories.nodes) {
-        // Aggregate language data
-        for (const language of repo.languages.edges) {
-          const languageName = language.node.name;
-          const size = language.size;
-
-          if (!languages[languageName]) {
-            languages[languageName] = 0;
-          }
-          languages[languageName] += size;
-          totalBytes += size;
-        }
-
-        // Aggregate commit data
-        if (repo.defaultBranchRef?.target?.history?.totalCount) {
-          totalCommits += repo.defaultBranchRef.target.history.totalCount;
-        }
-
-        // Collect most starred repositories
-        const mostUsedLanguage = repo.languages.edges[0]?.node.name || "Unknown";
-        mostStarredRepos.push({
-          name: repo.name,
-          stars: repo.stargazerCount,
-          url: repo.url,
-          description: repo.description || "No description available",
-          mostUsedLanguage,
-          forkCount: repo.forkCount,
-        });
+      for (const repo of repositories.nodes as RepoNode[]) {
+        aggregateRepoData(repo, languages, mostStarredRepos, totals);
       }
     }
 
-    // get additional single repos and merge them
+    // --- Fetch and merge extra repos ---
     for (const { owner, name } of extraRepos) {
       const singleRepoResponse = await client.query(GhSingleRepo, { owner, name }).toPromise();
-      if (singleRepoResponse.error) continue; // Fehlerhafte Repos überspringen
-      const repo = singleRepoResponse.data.repository;
-      if (!repo) continue;
-      for (const language of repo.languages.edges) {
-        const languageName = language.node.name;
-        const size = language.size;
-        if (!languages[languageName]) languages[languageName] = 0;
-        languages[languageName] += size;
-        totalBytes += size;
+      if (singleRepoResponse.error) {
+        console.warn(`Skipping extra repo ${owner}/${name}: ${singleRepoResponse.error.message}`);
+        continue;
       }
-      if (repo.defaultBranchRef?.target?.history?.totalCount) {
-        totalCommits += repo.defaultBranchRef.target.history.totalCount;
-      }
-      const mostUsedLanguage = repo.languages.edges[0]?.node.name || "Unknown";
-      mostStarredRepos.push({
-        name: repo.name,
-        stars: repo.stargazerCount,
-        url: repo.url,
-        description: repo.description || "No description available",
-        mostUsedLanguage,
-        forkCount: repo.forkCount,
-      });
+      const repo = singleRepoResponse.data.repository as RepoNode | undefined;
+      if (!repo) continue;
+      aggregateRepoData(repo, languages, mostStarredRepos, totals);
     }
 
-    // Sort repositories by stars and limit to top 6
-    mostStarredRepos = mostStarredRepos.sort((a, b) => b.stars - a.stars).slice(0, 6);
+    // --- Sort and limit starred repos ---
+    const topStarredRepos = mostStarredRepos.sort((a, b) => b.stars - a.stars).slice(0, 6);
 
-    // Calculate language percentages
-    const languagePercentages: { [key: string]: number } = {};
-    for (const [language, bytesOfCode] of Object.entries(languages)) {
-      languagePercentages[language] = (bytesOfCode / totalBytes) * 100;
-    }
-
-    // Sort languages by percentage descending
-    const sortedLanguages = Object.entries(languagePercentages)
-      .sort((a, b) => b[1] - a[1])
-      .map(([language, percentage]) => ({ language, percentage }));
+    // --- Calculate language percentages ---
+    const languagePercentages = Object.entries(languages)
+      .map(([language, bytesOfCode]) => ({
+        language,
+        percentage: (bytesOfCode / (totals.bytes || 1)) * 100,
+      }))
+      .sort((a, b) => b.percentage - a.percentage);
 
+    // --- Response ---
     return new Response(
       JSON.stringify({
-        languagePercentages: sortedLanguages,
-        totalBytes,
-        totalCommits,
-        mostStarredRepos,
+        languagePercentages,
+        totalBytes: totals.bytes,
+        totalCommits: totals.commits,
+        mostStarredRepos: topStarredRepos,
       }),
       {
         status: 200,
-        headers: {
-          "Content-Type": "application/json",
-        },
+        headers: { "Content-Type": "application/json" },
       },
     );
-  } catch (error) {
+  } catch (error: any) {
     console.error(`Error fetching data from API: ${error.message}`);
     return new Response(JSON.stringify({ error: error.message }), {
       status: 500,
-      headers: {
-        "Content-Type": "application/json",
-      },
+      headers: { "Content-Type": "application/json" },
     });
   }
 }
diff --git a/src/styles/components/_github-stats.scss b/src/styles/components/_github-stats.scss
index 524654e8..65c8b148 100644
--- a/src/styles/components/_github-stats.scss
+++ b/src/styles/components/_github-stats.scss
@@ -24,6 +24,7 @@ $langs: (
   kotlin: #a97bff,
   swift: #f05138,
   objective-c: #438eff,
+  xslt: #eb8ceb,
 );
 
 .c-github-stats-card {


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR refactors the GitHub stats endpoint to improve code organization and clarity while adding a new language color to the stats card styling.  
- Added a new language color ("xslt") to the SCSS file.  
- Introduced TypeScript types and an aggregateRepoData helper function in the API endpoint to clean up repository data aggregation and error handling.  
- Refined the repository fetching and extra repo merging logic, including improved logging for extra repos.

### Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

| File                                      | Description                                                                      |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| src/styles/components/_github-stats.scss  | Added a new "xslt" language color to the language mapping                         |
| src/pages/api/github/stats.ts             | Refactored aggregation logic with typed helper function and refined error handling |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T17:35:43Z

Review comments:
diff_hunk: @@ -53,134 +102,75 @@ export async function GET(context: APIContext): Promise<Response> {
         }
       }
     `;
-
     const userResponse = await client.query(USER_QUERY, {}).toPromise();
-
     if (userResponse.error) {
       throw new Error(`Error fetching user info: ${userResponse.error.message}`);
     }
-
     const username = userResponse.data.viewer.login;
 
-    // Fetch repositories and aggregate data
+    // --- Aggregation setup ---
     let hasNextPage = true;
     let after: string | null = null;
-    const languages: { [key: string]: number } = {};
-    let totalBytes = 0;
-    let totalCommits = 0;
-    let mostStarredRepos: {
-      name: string;
-      stars: number;
-      url: string;
-      description: string;
-      mostUsedLanguage: string;
-      forkCount: number;
-    }[] = [];
+    const languages: Record<string, number> = {};
+    const mostStarredRepos: RepoSummary[] = [];
+    const totals = { bytes: 0, commits: 0 };
 
+    // --- Fetch all user repos (paginated) ---
     while (hasNextPage) {
       const reposResponse = await client.query(GhRepos, { username, after }).toPromise();
-
       if (reposResponse.error) {
         throw new Error(`Error fetching repositories: ${reposResponse.error.message}`);
       }
-
       const repositories = reposResponse.data.user.repositories;
       hasNextPage = repositories.pageInfo.hasNextPage;
       after = repositories.pageInfo.endCursor;
 
-      for (const repo of repositories.nodes) {
-        // Aggregate language data
-        for (const language of repo.languages.edges) {
-          const languageName = language.node.name;
-          const size = language.size;
-
-          if (!languages[languageName]) {
-            languages[languageName] = 0;
-          }
-          languages[languageName] += size;
-          totalBytes += size;
-        }
-
-        // Aggregate commit data
-        if (repo.defaultBranchRef?.target?.history?.totalCount) {
-          totalCommits += repo.defaultBranchRef.target.history.totalCount;
-        }
-
-        // Collect most starred repositories
-        const mostUsedLanguage = repo.languages.edges[0]?.node.name || "Unknown";
-        mostStarredRepos.push({
-          name: repo.name,
-          stars: repo.stargazerCount,
-          url: repo.url,
-          description: repo.description || "No description available",
-          mostUsedLanguage,
-          forkCount: repo.forkCount,
-        });
+      for (const repo of repositories.nodes as RepoNode[]) {
+        aggregateRepoData(repo, languages, mostStarredRepos, totals);
       }
     }
 
-    // get additional single repos and merge them
+    // --- Fetch and merge extra repos ---
     for (const { owner, name } of extraRepos) {
       const singleRepoResponse = await client.query(GhSingleRepo, { owner, name }).toPromise();
-      if (singleRepoResponse.error) continue; // Fehlerhafte Repos überspringen
-      const repo = singleRepoResponse.data.repository;
-      if (!repo) continue;
-      for (const language of repo.languages.edges) {
-        const languageName = language.node.name;
-        const size = language.size;
-        if (!languages[languageName]) languages[languageName] = 0;
-        languages[languageName] += size;
-        totalBytes += size;
+      if (singleRepoResponse.error) {
+        console.warn(`Skipping extra repo ${owner}/${name}: ${singleRepoResponse.error.message}`);
+        continue;
       }
-      if (repo.defaultBranchRef?.target?.history?.totalCount) {
-        totalCommits += repo.defaultBranchRef.target.history.totalCount;
-      }
-      const mostUsedLanguage = repo.languages.edges[0]?.node.name || "Unknown";
-      mostStarredRepos.push({
-        name: repo.name,
-        stars: repo.stargazerCount,
-        url: repo.url,
-        description: repo.description || "No description available",
-        mostUsedLanguage,
-        forkCount: repo.forkCount,
-      });
+      const repo = singleRepoResponse.data.repository as RepoNode | undefined;
+      if (!repo) continue;
+      aggregateRepoData(repo, languages, mostStarredRepos, totals);
     }
 
-    // Sort repositories by stars and limit to top 6
-    mostStarredRepos = mostStarredRepos.sort((a, b) => b.stars - a.stars).slice(0, 6);
+    // --- Sort and limit starred repos ---
+    const topStarredRepos = mostStarredRepos.sort((a, b) => b.stars - a.stars).slice(0, 6);
 
-    // Calculate language percentages
-    const languagePercentages: { [key: string]: number } = {};
-    for (const [language, bytesOfCode] of Object.entries(languages)) {
-      languagePercentages[language] = (bytesOfCode / totalBytes) * 100;
-    }
-
-    // Sort languages by percentage descending
-    const sortedLanguages = Object.entries(languagePercentages)
-      .sort((a, b) => b[1] - a[1])
-      .map(([language, percentage]) => ({ language, percentage }));
+    // --- Calculate language percentages ---
+    const languagePercentages = Object.entries(languages)
+      .map(([language, bytesOfCode]) => ({
+        language,
+        percentage: (bytesOfCode / (totals.bytes || 1)) * 100,
+      }))
+      .sort((a, b) => b.percentage - a.percentage);
 
+    // --- Response ---
     return new Response(
       JSON.stringify({
-        languagePercentages: sortedLanguages,
-        totalBytes,
-        totalCommits,
-        mostStarredRepos,
+        languagePercentages,
+        totalBytes: totals.bytes,
+        totalCommits: totals.commits,
+        mostStarredRepos: topStarredRepos,
       }),
       {
         status: 200,
-        headers: {
-          "Content-Type": "application/json",
-        },
+        headers: { "Content-Type": "application/json" },
       },
     );
-  } catch (error) {
+  } catch (error: any) {
     console.error(`Error fetching data from API: ${error.message}`);
     return new Response(JSON.stringify({ error: error.message }), {
path: src/pages/api/github/stats.ts
user: login: Copilot
body: Consider using 'unknown' instead of 'any' in the catch clause for improved type safety and to avoid potential pitfalls with implicit assumptions about the error object.
```suggestion
  } catch (error: unknown) {
    const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
    console.error(`Error fetching data from API: ${errorMessage}`);
    return new Response(JSON.stringify({ error: errorMessage }), {
```
created_at: 2025-05-10T17:35:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -42,9 +63,37 @@ const client = new Client({
 // List of additional repos (owner/name)
 const extraRepos = [{ owner: "bitsundbaeume", name: "publication2023" }];
 
+// --- Helper: Aggregate repo data ---
+function aggregateRepoData(
+  repo: RepoNode,
+  languages: Record<string, number>,
+  mostStarredRepos: RepoSummary[],
+  totals: { bytes: number; commits: number },
+) {
+  for (const language of repo.languages.edges) {
+    const languageName = language.node.name;
+    const size = language.size;
+    if (!languages[languageName]) languages[languageName] = 0;
+    languages[languageName] += size;
+    totals.bytes += size;
+  }
path: src/pages/api/github/stats.ts
user: login: Copilot
body: [nitpick] Adding a brief comment to explain why checking for 'totalCount' via optional chaining is necessary here could improve code clarity for future maintainers.
```suggestion
  }
  // The optional chaining is necessary because the GitHub API might not always
  // include the `defaultBranchRef`, `target`, `history`, or `totalCount` properties.
```
created_at: 2025-05-10T17:35:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-10T17:36:08Z
body: ## [Codecov](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/pull/72?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze) Report
Attention: Patch coverage is `0%` with `55 lines` in your changes missing coverage. Please review.
> Project coverage is 36.94%. Comparing base [(`5933e02`)](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/commit/5933e02965c91818af18db9005119163b2a61db7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze) to head [(`ce77304`)](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/commit/ce773043e5ae2a5f4432859aeaace82d785b5dbc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze).
> Report is 4 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/pull/72?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze) | Patch % | Lines |
|---|---|---|
| [src/pages/api/github/stats.ts](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/pull/72?src=pr&el=tree&filepath=src%2Fpages%2Fapi%2Fgithub%2Fstats.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze#diff-c3JjL3BhZ2VzL2FwaS9naXRodWIvc3RhdHMudHM=) | 0.00% | [55 Missing :warning: ](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/pull/72?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main      #72      +/-   ##
==========================================
+ Coverage   36.76%   36.94%   +0.17%     
==========================================
  Files         114      114              
  Lines        3824     3806      -18     
  Branches      279      279              
==========================================
  Hits         1406     1406              
+ Misses       2348     2330      -18     
  Partials       70       70              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/pull/72?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Felix+Scholze).

author_association: NONE
user: login: felix-berlin
created_at: 2025-05-10T17:39:36Z
body: :tada: This PR is included in version 1.7.0 :tada:

The release is available on [GitHub release](https://github.com/felix-berlin/webshaped-blog-astro/releases/tag/v1.7.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: OWNER
user: login: codecov[bot]
created_at: 2025-05-12T14:06:58Z
body: ## [Bundle](https://app.codecov.io/gh/felix-berlin/webshaped-blog-astro/pull/72?dropdown=bundle) Report

Changes will decrease total bundle size by 200 bytes (-0.01%) :arrow_down:. This is within the [configured](https://docs.codecov.com/docs/javascript-bundle-analysis#main-features) threshold :white_check_mark:

<details><summary>Detailed changes</summary>

| Bundle name | Size | Change |
| ----------- | ---- | ------ |
| web-shaped-bundle-server-esm | 1.12MB | -1.32kB (-0.12%) :arrow_down: |
| web-shaped-bundle-client-esm | 647.82kB | 1.12kB (0.17%) :arrow_up: |

</details>

### Affected Assets, Files, and Routes:

<details>
<summary>view changes for bundle: web-shaped-bundle-server-esm</summary>

#### **Assets Changed:**
| Asset Name | Size Change | Total Size | Change (%) |
| ---------- | ----------- | ---------- | ---------- |
| **```chunks/graphql_BaUKZErL.mjs```** _(New)_ | 114.13kB | 114.13kB | 100.0% :rocket: |
| **```chunks/astro/server_Cx47wVIO.mjs```** _(New)_ | 102.29kB | 102.29kB | 100.0% :rocket: |
| ```pages/_lang_/posts/_postslug_.astro.mjs``` | -1 bytes | 99.15kB | -0.0%  |
| **```chunks/index_XA9XvQ3g.mjs```** _(New)_ | 79.38kB | 79.38kB | 100.0% :rocket: |
| **```chunks/_astro_assets_DXRaGTXZ.mjs```** _(New)_ | 50.05kB | 50.05kB | 100.0% :rocket: |
| **```chunks/_@astrojs-ssr-adapter_D49tpTTj.mjs```** _(New)_ | 48.98kB | 48.98kB | 100.0% :rocket: |
| **```chunks/DefaultLayout_CwgudFQQ.mjs```** _(New)_ | 41.02kB | 41.02kB | 100.0% :rocket: |
| ```pages/api/github/stats.astro.mjs``` | -825 bytes | 14.69kB | -5.32%  |
| **```chunks/astro-designed-error-pages_DEXJIJA1.mjs```** _(New)_ | 11.86kB | 11.86kB | 100.0% :rocket: |
| **```chunks/server_DPiNSpWj.mjs```** _(New)_ | 6.39kB | 6.39kB | 100.0% :rocket: |
| **```chunks/_@astro-renderers_Cet833XL.mjs```** _(New)_ | 5.51kB | 5.51kB | 100.0% :rocket: |
| **```manifest_DHf8-XNs.mjs```** _(New)_ | 4.04kB | 4.04kB | 100.0% :rocket: |
| **```chunks/BlogPostPreview_DjrOLiWz.mjs```** _(New)_ | 3.13kB | 3.13kB | 100.0% :rocket: |
| **```chunks/sharp_OLw0SUvX.mjs```** _(New)_ | 2.99kB | 2.99kB | 100.0% :rocket: |
| **```chunks/runtime_CzNuqNWi.mjs```** _(New)_ | 1.53kB | 1.53kB | 100.0% :rocket: |
| **```chunks/noop-middleware_g72OPydQ.mjs```** _(New)_ | 714 bytes | 714 bytes | 100.0% :rocket: |
| **```chunks/astro_DrV0k6io.mjs```** _(New)_ | 505 bytes | 505 bytes | 100.0% :rocket: |
| ~~**```chunks/graphql_BVSaWUtC.mjs```**~~ _(Deleted)_ | -114.13kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/astro/server_C-NkeqMX.mjs```**~~ _(Deleted)_ | -102.29kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/index_CJPBdzBw.mjs```**~~ _(Deleted)_ | -79.38kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/_astro_assets_jfM4YFXI.mjs```**~~ _(Deleted)_ | -50.05kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/_@astrojs-ssr-adapter_rtUPlLWU.mjs```**~~ _(Deleted)_ | -48.98kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/DefaultLayout_ew08U1Bi.mjs```**~~ _(Deleted)_ | -41.52kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/astro-designed-error-pages_-_vodNV1.mjs```**~~ _(Deleted)_ | -11.86kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/server_DNOHDR28.mjs```**~~ _(Deleted)_ | -6.39kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/_@astro-renderers_B6iUajfe.mjs```**~~ _(Deleted)_ | -5.51kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```manifest_ebl8U6j_.mjs```**~~ _(Deleted)_ | -4.04kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/BlogPostPreview_C7vv62hc.mjs```**~~ _(Deleted)_ | -3.13kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/sharp_CtbDcVjK.mjs```**~~ _(Deleted)_ | -2.99kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/runtime_BicvAnBH.mjs```**~~ _(Deleted)_ | -1.53kB | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/noop-middleware_DmJvsWMJ.mjs```**~~ _(Deleted)_ | -714 bytes | 0 bytes | -100.0% :wastebasket: |
| ~~**```chunks/astro_kevZRyES.mjs```**~~ _(Deleted)_ | -505 bytes | 0 bytes | -100.0% :wastebasket: |
















**Files in** **```pages/api/github/stats.astro.mjs```**:

- ```./src/pages/api/github/stats.ts``` → Total Size: **3.87kB**




















































#### App Routes Affected:

| App Route | Size Change | Total Size | Change (%) |
| --------- | ----------- | ---------- | ---------- |
| /api/github/stats | -825 bytes | 14.69kB | -5.32%  |
| /[lang]/posts/[postSlug] | -1 bytes | 99.15kB | -0.0%  |


</details>

<details>
<summary>view changes for bundle: web-shaped-bundle-client-esm</summary>

#### **Assets Changed:**
| Asset Name | Size Change | Total Size | Change (%) |
| ---------- | ----------- | ---------- | ---------- |
| ```_astro/GithubStats.*.css``` | 1.09kB | 6.66kB | 19.68% :warning: |
| ```_astro/CommentItem.*.js``` | 5 bytes | 4.85kB | 0.1%  |
| ```_astro/LoadWebmentions.*.js``` | -5 bytes | 2.72kB | -0.18%  |
| ```_astro/StoreSetter.*.js``` | 24 bytes | 938 bytes | 2.63%  |
| **```_astro/Search.*.DXyY1bE-.js```** _(New)_ | 898 bytes | 898 bytes | 100.0% :rocket: |
| ~~**```_astro/Search.*.BZNplxqE.js```**~~ _(Deleted)_ | -898 bytes | 0 bytes | -100.0% :wastebasket: |
















</details>

author_association: NONE