Refactor GitHub Stats Action to use Bun, update dependencies, and enhance README with detailed usage instructions. Bump version to 0.0.6.

This commit is contained in:
Luke Hagar
2025-12-04 20:54:52 +00:00
parent eba2296e9d
commit f70e248a3b
20 changed files with 1297 additions and 46852 deletions

View File

@@ -7,32 +7,36 @@ on:
workflow_dispatch:
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Run tests
run: bun test
- name: Type check
run: bun run typecheck
release:
runs-on: ubuntu-latest
needs: test
concurrency:
group: release-action
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v2
with:
node-version: "20"
- name: Run install
uses: borales/actions-yarn@v5
with:
cmd: install
- name: Run build
uses: borales/actions-yarn@v5
with:
cmd: build
- uses: actions/checkout@v4
- name: Get package info
id: package-info
@@ -40,11 +44,6 @@ jobs:
with:
path: .
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore(release): ${{ steps.package-info.outputs.version }}"
branch: main
- name: Publish Draft Release if doesn't exist
run: |
if ! gh release view ${{ steps.package-info.outputs.version }} >/dev/null 2>&1; then

1
.gitignore vendored
View File

@@ -1,3 +1,2 @@
node_modules
lib
.env

223
README.md
View File

@@ -1,13 +1,226 @@
# GitHub Stats Action
A GitHub action to automate the collection of stats for a users GitHub profile.
A GitHub Action that collects comprehensive statistics for a user's GitHub profile and outputs them to a JSON file. Perfect for building profile READMEs, dashboards, or personal analytics.
## Features
- **Profile Data**: Name, bio, company, location, social links
- **Contribution Stats**: Total contributions, streaks, most active day, monthly breakdown
- **Repository Metrics**: Stars, forks, views, top repositories
- **Code Statistics**: Lines added/deleted, commit counts, languages with percentages
- **Social Stats**: Followers, following, stars given
- **Activity Data**: Pull requests, issues, PR reviews, discussions
## Requirements
### Personal Access Token (PAT)
This action **requires a Personal Access Token** - the default `GITHUB_TOKEN` will not work because:
- Contribution calendar data requires authentication as the actual user
- Repository view counts require push access across all repositories
- The `viewer` GraphQL query returns data for the token owner
#### Required PAT Scopes
| Scope | Purpose |
|-------|---------|
| `read:user` | Access user profile data |
| `repo` | Access repository statistics, views, and private repos |
#### Creating a PAT
1. Go to [GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens](https://github.com/settings/tokens?type=beta)
2. Click "Generate new token"
3. Select the scopes listed above
4. Copy the token and add it as a repository secret (e.g., `USER_PAT`)
## Usage
Please see [this workflow](https://github.com/LukeHagar/stats/blob/23adb0a0cb2a283a30300478a2834b9766440b05/.github/workflows/generate-stats.yaml#L24C15-L24C37) to see this Action in use
### Basic Workflow
When this action is run it creates a `github-stats.json` file in the root of the repository.
Create `.github/workflows/stats.yaml`:
This file contains all of the GitHub stats collected for the user.
```yaml
name: Collect GitHub Stats
You can use [this template repository](https://github.com/LukeHagar/stats/) and follow the [instructions in the Readme](https://github.com/LukeHagar/stats/?tab=readme-ov-file#github-repo-template) to collect stats for yourself.
on:
schedule:
- cron: '0 0 * * *' # Run daily at midnight
workflow_dispatch: # Allow manual trigger
jobs:
collect-stats:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Collect GitHub Stats
uses: LukeHagar/stats-action@v1
env:
GITHUB_TOKEN: ${{ secrets.USER_PAT }}
- name: Commit stats
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "chore: update github stats"
file_pattern: github-user-stats.json
```
### Template Repository
For a complete setup with visualization, use the [stats template repository](https://github.com/LukeHagar/stats/).
## Output
The action creates a `github-user-stats.json` file with the following structure:
### Profile Information
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Display name |
| `username` | string | GitHub username |
| `avatarUrl` | string | Profile picture URL |
| `bio` | string \| null | Profile bio |
| `company` | string \| null | Company name |
| `location` | string \| null | Location |
| `email` | string \| null | Public email |
| `twitterUsername` | string \| null | Twitter/X handle |
| `websiteUrl` | string \| null | Website URL |
| `createdAt` | string | Account creation date (ISO 8601) |
### Statistics
| Field | Type | Description |
|-------|------|-------------|
| `totalCommits` | number | Total commits (from GitHub search) |
| `commitCount` | number | Commits from contributor stats |
| `totalPullRequests` | number | Total PRs created |
| `totalPullRequestReviews` | number | Total PR reviews |
| `totalContributions` | number | All-time contributions |
| `openIssues` | number | Open issues created |
| `closedIssues` | number | Closed issues created |
| `discussionsStarted` | number | Discussions started |
| `discussionsAnswered` | number | Discussion answers marked as correct |
| `repositoriesContributedTo` | number | Repos contributed to |
### Repository Metrics
| Field | Type | Description |
|-------|------|-------------|
| `starCount` | number | Total stars received |
| `forkCount` | number | Total forks of your repos |
| `starsGiven` | number | Repos you've starred |
| `repoViews` | number | Total repo views (last 14 days) |
### Code Statistics
| Field | Type | Description |
|-------|------|-------------|
| `linesAdded` | number | Total lines added |
| `linesDeleted` | number | Total lines deleted |
| `linesOfCodeChanged` | number | Total lines changed (added + deleted) |
| `codeByteTotal` | number | Total bytes of code |
### Social
| Field | Type | Description |
|-------|------|-------------|
| `followers` | number | Follower count |
| `following` | number | Following count |
### Languages
```json
{
"topLanguages": [
{
"languageName": "TypeScript",
"color": "#3178c6",
"value": 1234567,
"percentage": 45.5
}
]
}
```
### Contribution Stats
```json
{
"contributionStats": {
"longestStreak": 42,
"currentStreak": 7,
"mostActiveDay": "Tuesday",
"averagePerDay": 3.5,
"averagePerWeek": 24.5,
"averagePerMonth": 105.0,
"monthlyBreakdown": [
{ "month": "2024-01", "contributions": 120 }
]
}
}
```
### Top Repositories
```json
{
"topRepos": [
{
"name": "repo-name",
"description": "Repo description",
"stars": 100,
"forks": 25,
"isArchived": false,
"primaryLanguage": "TypeScript",
"updatedAt": "2024-01-15T10:30:00Z",
"createdAt": "2023-06-01T08:00:00Z"
}
]
}
```
### Full Contribution Calendar
The `contributionsCollection` field contains the complete contribution calendar with daily data for building heatmaps.
## Development
### Prerequisites
- [Bun](https://bun.sh) v1.0+
### Setup
```bash
# Install dependencies
bun install
# Run locally (requires GITHUB_TOKEN env var)
export GITHUB_TOKEN=your_pat_here
bun run start
# Run tests
bun test
# Type check
bun run typecheck
```
### Project Structure
```
├── src/
│ ├── index.ts # Main action logic
│ ├── index.test.ts # Tests
│ └── Types.ts # TypeScript type definitions
├── action.yml # GitHub Action definition
├── package.json
└── tsconfig.json
```
## License
MIT - see [LICENSE.md](LICENSE.md)

View File

@@ -4,5 +4,18 @@ branding:
icon: "bar-chart-2"
color: "blue"
runs:
using: "node20"
main: "./dist/index.js"
using: "composite"
steps:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
shell: bash
run: bun install --frozen-lockfile
working-directory: ${{ github.action_path }}
- name: Run stats collection
shell: bash
run: bun run ${{ github.action_path }}/src/index.ts

View File

@@ -1,11 +0,0 @@
export const NOT_LANGUAGES = [
'html',
'markdown',
'dockerfile',
'roff',
'rich text format',
'powershell',
'css',
'php',
];
//# sourceMappingURL=Types.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"Types.js","sourceRoot":"","sources":["../src/Types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG;IAC5B,MAAM;IACN,UAAU;IACV,YAAY;IACZ,MAAM;IACN,kBAAkB;IAClB,YAAY;IACZ,KAAK;IACL,KAAK;CACL,CAAC"}

View File

@@ -1,114 +0,0 @@
import core from "@actions/core";
import { Octokit } from "octokit";
import { config } from "dotenv";
import { getContributionCollection, getGraphQLData, getReposContributorsStats, getReposViewCount, getTotalCommits, } from "./octokit";
import { writeFileSync } from "fs";
config();
export const NOT_LANGUAGES = [
"html",
"markdown",
"dockerfile",
"roff",
"rich text format",
"powershell",
"css",
"php",
];
const NOT_LANGUAGES_OBJ = Object.fromEntries(NOT_LANGUAGES.map((l) => [l, true]));
try {
const token = process.env["GITHUB_TOKEN"];
if (!token)
throw new Error("GITHUB_TOKEN is not present");
const octokit = new Octokit({ auth: token });
const fetchedAt = Date.now();
const userDetails = await octokit.rest.users.getAuthenticated();
const username = userDetails.data.login;
const accountCreationDate = userDetails.data.created_at;
const [graphQLData, totalCommits, contributionsCollection] = await Promise.all([
getGraphQLData(octokit, username),
getTotalCommits(octokit, username),
getContributionCollection(octokit, accountCreationDate),
]);
console.log(userDetails);
console.log(graphQLData);
console.log(totalCommits);
console.log(contributionsCollection);
let starCount = 0;
let forkCount = 0;
for (const repo of graphQLData.user.repositories.nodes) {
starCount += repo.stargazers.totalCount;
forkCount += repo.forkCount;
}
const contributorStatsPromises = [];
const viewCountPromises = [];
for (const repo of graphQLData.user.repositories.nodes) {
contributorStatsPromises.push(getReposContributorsStats(octokit, username, repo.name));
viewCountPromises.push(getReposViewCount(octokit, username, repo.name));
}
const contributorStats = (await Promise.all(contributorStatsPromises))
.filter((entry) => entry !== null || entry !== undefined)
.map((entry) => {
return (Array.isArray(entry.data) ? entry.data : [entry.data])
.filter((contributor) => contributor.author?.login === userDetails.data.login)
.map((contributor) => contributor.weeks);
});
let linesOfCodeChanged = 0;
for (const repo of contributorStats) {
for (const week of repo) {
for (const day of week) {
linesOfCodeChanged += (day.a || 0) + (day.d || 0) + (day.c || 0);
}
}
}
const viewCounts = await Promise.all(viewCountPromises);
let repoViews = 0;
for (const viewCount of viewCounts) {
repoViews += viewCount.data.count;
}
const topLanguages = [];
let codeByteTotal = 0;
for (const node of graphQLData.user.repositories.nodes) {
for (const edge of node.languages.edges) {
if (NOT_LANGUAGES_OBJ[edge.node.name.toLowerCase()]) {
continue;
}
const existingLanguage = topLanguages.find((l) => l.languageName === edge.node.name);
if (existingLanguage) {
existingLanguage.value += edge.size;
codeByteTotal += edge.size;
}
else {
topLanguages.push({
languageName: edge.node.name,
color: edge.node.color,
value: edge.size,
});
codeByteTotal += edge.size;
}
}
}
const allDays = contributionsCollection.contributionCalendar.weeks
.map((w) => w.contributionDays)
.flat(1);
writeFileSync("github-user-stats.json", JSON.stringify({
name: userDetails.data.name || "",
username,
repoViews,
linesOfCodeChanged,
totalCommits: totalCommits.data.total_count,
totalPullRequests: graphQLData.user.pullRequests.totalCount,
codeByteTotal,
topLanguages,
forkCount,
starCount,
totalContributions: contributionsCollection.contributionCalendar.totalContributions,
closedIssues: graphQLData.viewer.closedIssues.totalCount,
openIssues: graphQLData.viewer.openIssues.totalCount,
fetchedAt,
contributionData: allDays,
}, null, 4));
}
catch (error) {
core.setFailed(error);
}
//# sourceMappingURL=index.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}

View File

@@ -1,195 +0,0 @@
export async function getGraphQLData(octokit, username) {
return octokit.graphql.paginate(`query userInfo($login: String!, $cursor: String) {
user(login: $login) {
name
login
repositories(
orderBy: {field: STARGAZERS, direction: DESC}
ownerAffiliations: OWNER
isFork: false
first: 100
after: $cursor
) {
totalCount
nodes {
stargazers {
totalCount
}
forkCount
name
languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {
edges {
size
node {
color
name
}
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
pullRequests(first: 1) {
totalCount
}
}
viewer {
openIssues: issues(states: OPEN) {
totalCount
}
closedIssues: issues(states: CLOSED) {
totalCount
}
}
rateLimit {
limit
remaining
used
resetAt
}
}`, {
login: username,
});
}
export async function getContributionCollection(octokit, year) {
const yearCreated = new Date(year);
const currentYear = new Date();
const promises = [];
for (let i = yearCreated.getFullYear(); i <= currentYear.getFullYear(); i++) {
let startYear = `${i}-01-01T00:00:00.000Z`;
if (i === yearCreated.getFullYear())
startYear = year;
let endYear = `${i + 1}-01-01T00:00:00.000Z`;
if (i === currentYear.getFullYear())
endYear = currentYear.toISOString();
promises.push(octokit
.graphql(`query {
rateLimit {
limit
remaining
used
resetAt
}
viewer {
contributionsCollection(from: "${startYear}", to: "${endYear}") {
totalCommitContributions
restrictedContributionsCount
totalIssueContributions
totalCommitContributions
totalRepositoryContributions
totalPullRequestContributions
totalPullRequestReviewContributions
popularPullRequestContribution {
pullRequest {
id
title
repository {
name
owner {
login
}
}
}
}
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
commitContributionsByRepository {
contributions {
totalCount
}
repository {
name
owner {
login
}
languages(first: 5, orderBy: { field: SIZE, direction: DESC }) {
edges {
size
node {
color
name
id
}
}
}
}
}
}
}
}
`)
.catch((error) => {
throw new Error(`Failed to fetch data for year ${i}: ${error.message}`);
}));
}
console.log(promises);
const years = (await Promise.all(promises)).filter(Boolean);
console.debug(years);
if (years.length === 0) {
throw new Error('Failed to fetch data for all years');
}
const { contributionsCollection } = years[0].viewer;
for (const year of years.slice(1)) {
contributionsCollection.commitContributionsByRepository = [
...contributionsCollection.commitContributionsByRepository,
...year.viewer.contributionsCollection.commitContributionsByRepository,
];
contributionsCollection.contributionCalendar.totalContributions +=
year.viewer.contributionsCollection.contributionCalendar.totalContributions;
contributionsCollection.contributionCalendar.weeks = [
...contributionsCollection.contributionCalendar.weeks,
...year.viewer.contributionsCollection.contributionCalendar.weeks,
];
}
return contributionsCollection;
}
export async function getTotalCommits(octokit, username) {
return octokit.rest.search.commits({
q: `author:${username}`,
});
}
export async function getUsersStars(octokit, username) {
return octokit.rest.activity.listReposStarredByUser({
username,
});
}
export async function getReposContributorsStats(octokit, username, repo) {
return octokit.rest.repos
.getContributorsStats({
owner: username,
repo,
})
.then((res) => {
if (res.status === 202) {
setTimeout(() => {
return octokit.rest.repos.getContributorsStats({
owner: username,
repo,
});
}, 2000);
}
return res;
})
.catch((error) => {
throw new Error(`Failed to fetch data for repo ${repo}: ${error.message}`);
});
}
export async function getReposViewCount(octokit, username, repo) {
return octokit.rest.repos.getViews({
per: 'week',
owner: username,
repo,
});
}
//# sourceMappingURL=octokit.js.map

View File

@@ -1 +0,0 @@
{"version":3,"file":"octokit.js","sourceRoot":"","sources":["../src/octokit.ts"],"names":[],"mappings":"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"}

165
bun.lock Normal file
View File

@@ -0,0 +1,165 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "github-profile-stats",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@octokit/action": "^6.0.7",
"dotenv": "^16.4.5",
"octokit": "^3.1.2",
},
"devDependencies": {
"@types/bun": "latest",
},
},
},
"packages": {
"@actions/core": ["@actions/core@1.10.1", "", { "dependencies": { "@actions/http-client": "^2.0.1", "uuid": "^8.3.2" } }, "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g=="],
"@actions/github": ["@actions/github@6.0.0", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-rest-endpoint-methods": "^10.0.0" } }, "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g=="],
"@actions/http-client": ["@actions/http-client@2.2.0", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg=="],
"@fastify/busboy": ["@fastify/busboy@2.1.0", "", {}, "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA=="],
"@octokit/action": ["@octokit/action@6.0.7", "", { "dependencies": { "@octokit/auth-action": "^4.0.0", "@octokit/core": "^5.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-rest-endpoint-methods": "^10.0.0", "@octokit/types": "^12.0.0", "undici": "^6.0.0" } }, "sha512-0Q1L96F8JsNb+M2NzN7r4artGyX02Pa9tzg+JaxXncvdHEXVIJFnkA8CstC52EB4DAZ0b8wpqDOG05v/DcyS3g=="],
"@octokit/app": ["@octokit/app@14.0.2", "", { "dependencies": { "@octokit/auth-app": "^6.0.0", "@octokit/auth-unauthenticated": "^5.0.0", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/types": "^12.0.0", "@octokit/webhooks": "^12.0.4" } }, "sha512-NCSCktSx+XmjuSUVn2dLfqQ9WIYePGP95SDJs4I9cn/0ZkeXcPkaoCLl64Us3dRKL2ozC7hArwze5Eu+/qt1tg=="],
"@octokit/auth-action": ["@octokit/auth-action@4.0.1", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/types": "^12.0.0" } }, "sha512-mJLOcFFafIivLZ7BEkGDCTFoHPJv7BeL5Zwy7j5qMDU0b/DKshhi6GCU9tw3vmKhOxTNquYfvwqsEfPpemaaxg=="],
"@octokit/auth-app": ["@octokit/auth-app@6.0.4", "", { "dependencies": { "@octokit/auth-oauth-app": "^7.0.0", "@octokit/auth-oauth-user": "^4.0.0", "@octokit/request": "^8.0.2", "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "deprecation": "^2.3.1", "lru-cache": "^10.0.0", "universal-github-app-jwt": "^1.1.2", "universal-user-agent": "^6.0.0" } }, "sha512-TPmJYgd05ok3nzHj7Y6we/V7Ez1wU3ztLFW3zo/afgYFtqYZg0W7zb6Kp5ag6E85r8nCE1JfS6YZoZusa14o9g=="],
"@octokit/auth-oauth-app": ["@octokit/auth-oauth-app@7.0.1", "", { "dependencies": { "@octokit/auth-oauth-device": "^6.0.0", "@octokit/auth-oauth-user": "^4.0.0", "@octokit/request": "^8.0.2", "@octokit/types": "^12.0.0", "@types/btoa-lite": "^1.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg=="],
"@octokit/auth-oauth-device": ["@octokit/auth-oauth-device@6.0.1", "", { "dependencies": { "@octokit/oauth-methods": "^4.0.0", "@octokit/request": "^8.0.0", "@octokit/types": "^12.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw=="],
"@octokit/auth-oauth-user": ["@octokit/auth-oauth-user@4.0.1", "", { "dependencies": { "@octokit/auth-oauth-device": "^6.0.0", "@octokit/oauth-methods": "^4.0.0", "@octokit/request": "^8.0.2", "@octokit/types": "^12.0.0", "btoa-lite": "^1.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw=="],
"@octokit/auth-token": ["@octokit/auth-token@4.0.0", "", {}, "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA=="],
"@octokit/auth-unauthenticated": ["@octokit/auth-unauthenticated@5.0.1", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0" } }, "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg=="],
"@octokit/core": ["@octokit/core@5.1.0", "", { "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.0.0", "@octokit/request": "^8.0.2", "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g=="],
"@octokit/endpoint": ["@octokit/endpoint@9.0.4", "", { "dependencies": { "@octokit/types": "^12.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw=="],
"@octokit/graphql": ["@octokit/graphql@7.0.2", "", { "dependencies": { "@octokit/request": "^8.0.1", "@octokit/types": "^12.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q=="],
"@octokit/oauth-app": ["@octokit/oauth-app@6.1.0", "", { "dependencies": { "@octokit/auth-oauth-app": "^7.0.0", "@octokit/auth-oauth-user": "^4.0.0", "@octokit/auth-unauthenticated": "^5.0.0", "@octokit/core": "^5.0.0", "@octokit/oauth-authorization-url": "^6.0.2", "@octokit/oauth-methods": "^4.0.0", "@types/aws-lambda": "^8.10.83", "universal-user-agent": "^6.0.0" } }, "sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g=="],
"@octokit/oauth-authorization-url": ["@octokit/oauth-authorization-url@6.0.2", "", {}, "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA=="],
"@octokit/oauth-methods": ["@octokit/oauth-methods@4.0.1", "", { "dependencies": { "@octokit/oauth-authorization-url": "^6.0.2", "@octokit/request": "^8.0.2", "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "btoa-lite": "^1.0.0" } }, "sha512-1NdTGCoBHyD6J0n2WGXg9+yDLZrRNZ0moTEex/LSPr49m530WNKcCfXDghofYptr3st3eTii+EHoG5k/o+vbtw=="],
"@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="],
"@octokit/plugin-paginate-graphql": ["@octokit/plugin-paginate-graphql@4.0.0", "", { "peerDependencies": { "@octokit/core": ">=5" } }, "sha512-7HcYW5tP7/Z6AETAPU14gp5H5KmCPT3hmJrS/5tO7HIgbwenYmgw4OY9Ma54FDySuxMwD+wsJlxtuGWwuZuItA=="],
"@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.0", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": ">=5" } }, "sha512-NKi0bJEZqOSbBLMv9kdAcuocpe05Q2xAXNLTGi0HN2GSMFJHNZuSoPNa0tcQFTOFCKe+ZaYBZ3lpXh1yxgUDCA=="],
"@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.0", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": ">=5" } }, "sha512-INw5rGXWlbv/p/VvQL63dhlXr38qYTHkQ5bANi9xofrF9OraqmjHsIGyenmjmul1JVRHpUlw5heFOj1UZLEolA=="],
"@octokit/plugin-retry": ["@octokit/plugin-retry@6.0.1", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": ">=5" } }, "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog=="],
"@octokit/plugin-throttling": ["@octokit/plugin-throttling@8.2.0", "", { "dependencies": { "@octokit/types": "^12.2.0", "bottleneck": "^2.15.3" }, "peerDependencies": { "@octokit/core": "^5.0.0" } }, "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ=="],
"@octokit/request": ["@octokit/request@8.2.0", "", { "dependencies": { "@octokit/endpoint": "^9.0.0", "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ=="],
"@octokit/request-error": ["@octokit/request-error@5.0.1", "", { "dependencies": { "@octokit/types": "^12.0.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ=="],
"@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="],
"@octokit/webhooks": ["@octokit/webhooks@12.1.2", "", { "dependencies": { "@octokit/request-error": "^5.0.0", "@octokit/webhooks-methods": "^4.1.0", "@octokit/webhooks-types": "7.3.2", "aggregate-error": "^3.1.0" } }, "sha512-+nGS3ReCByF6m+nbNB59x7Aa3CNjCCGuBLFzfkiJP1O3uVKKuJbkP4uO4t46YqH26nlugmOhqjT7nx5D0VPtdA=="],
"@octokit/webhooks-methods": ["@octokit/webhooks-methods@4.1.0", "", {}, "sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ=="],
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.3.2", "", {}, "sha512-JWOoOgtWTFnTSAamPXXyjTY5/apttvNxF+vPBnwdSu5cj5snrd7FO0fyw4+wTXy8fHduq626JjhO+TwCyyA6vA=="],
"@types/aws-lambda": ["@types/aws-lambda@8.10.134", "", {}, "sha512-cfv422ivDMO+EeA3N4YcshbTHBL+5lLXe+Uz+4HXvIcsCuWvqNFpOs28ZprL8NA3qRCzt95ETiNAJDn4IcC/PA=="],
"@types/btoa-lite": ["@types/btoa-lite@1.0.2", "", {}, "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg=="],
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
"@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw=="],
"@types/node": ["@types/node@20.11.20", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg=="],
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
"btoa-lite": ["btoa-lite@1.0.0", "", {}, "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
"clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
"dotenv": ["dotenv@16.4.5", "", {}, "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
"jwa": ["jwa@1.4.1", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA=="],
"jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"lru-cache": ["lru-cache@10.2.0", "", {}, "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"octokit": ["octokit@3.1.2", "", { "dependencies": { "@octokit/app": "^14.0.2", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-graphql": "^4.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/plugin-rest-endpoint-methods": "^10.0.0", "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.0.0", "@octokit/request-error": "^5.0.0", "@octokit/types": "^12.0.0" } }, "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"semver": ["semver@7.6.0", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": "bin/semver.js" }, "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="],
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
"undici": ["undici@6.6.2", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-vSqvUE5skSxQJ5sztTZ/CdeJb1Wq0Hf44hlYMciqHghvz+K88U0l7D6u1VsndoFgskDcnU+nG3gYmMzJVzd9Qg=="],
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"universal-github-app-jwt": ["universal-github-app-jwt@1.1.2", "", { "dependencies": { "@types/jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2" } }, "sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA=="],
"universal-user-agent": ["universal-user-agent@6.0.1", "", {}, "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="],
"uuid": ["uuid@8.3.2", "", { "bin": "dist/bin/uuid" }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"@actions/http-client/undici": ["undici@5.28.3", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA=="],
"semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
}
}

45663
dist/index.js vendored

File diff suppressed because one or more lines are too long

3
dist/package.json vendored
View File

@@ -1,3 +0,0 @@
{
"type": "module"
}

View File

@@ -1,26 +1,21 @@
{
"name": "github-profile-stats",
"private": true,
"version": "0.0.5",
"version": "0.0.6",
"type": "module",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@octokit/action": "^6.0.7",
"@vercel/ncc": "^0.38.1",
"dotenv": "^16.4.5",
"octokit": "^3.1.2",
"typescript": "^5.3.3"
"octokit": "^3.1.2"
},
"devDependencies": {
"@types/bun": "latest"
},
"scripts": {
"br": "yarn build && yarn start",
"build": "tsc && ncc build lib/index.js",
"start": "node dist/index.js",
"patch": "yarn version --patch",
"minor": "yarn version --minor",
"major": "yarn version --major"
},
"type": "module",
"devDependencies": {
"@types/node": "^20.11.20"
"start": "bun run src/index.ts",
"test": "bun test",
"typecheck": "bun x tsc --noEmit"
}
}

View File

@@ -1,40 +1,8 @@
export const NOT_LANGUAGES = [
"html",
"markdown",
"dockerfile",
"roff",
"rich text format",
"powershell",
"css",
"php",
];
export type UserStats = {
name: string;
username: string;
repoViews: number;
linesOfCodeChanged: number;
totalCommits: number;
totalPullRequests: number;
openIssues: number;
closedIssues: number;
fetchedAt: number;
forkCount: number;
starCount: number;
totalContributions: number;
codeByteTotal: number;
topLanguages: Array<{
languageName: string;
color: string | null;
value: number;
}>;
contributionData: Array<{ contributionCount: number; date: string }>;
};
export type Language = {
languageName: string;
color: string | null;
value: number;
percentage: number;
};
export type ContributionData = {
@@ -60,9 +28,96 @@ export type ContributionsCollection = {
};
};
export type MonthlyContribution = {
month: string; // YYYY-MM format
contributions: number;
};
export type ContributionStats = {
longestStreak: number;
currentStreak: number;
mostActiveDay: string; // Day of week
averagePerDay: number;
averagePerWeek: number;
averagePerMonth: number;
monthlyBreakdown: MonthlyContribution[];
};
export type RateLimitInfo = {
limit: number;
remaining: number;
used: number;
resetAt: string;
};
export type UserProfile = {
name: string;
login: string;
bio: string | null;
company: string | null;
location: string | null;
email: string | null;
twitterUsername: string | null;
websiteUrl: string | null;
avatarUrl: string;
createdAt: string;
followers: number;
following: number;
};
export type RepoDetails = {
name: string;
description: string | null;
stars: number;
forks: number;
isArchived: boolean;
primaryLanguage: string | null;
updatedAt: string;
createdAt: string;
};
export type UserStats = {
name: string;
username: string;
avatarUrl: string;
bio: string | null;
company: string | null;
location: string | null;
email: string | null;
twitterUsername: string | null;
websiteUrl: string | null;
createdAt: string;
repoViews: number;
linesOfCodeChanged: number;
linesAdded: number;
linesDeleted: number;
commitCount: number;
totalCommits: number;
totalPullRequests: number;
totalPullRequestReviews: number;
openIssues: number;
closedIssues: number;
fetchedAt: number;
forkCount: number;
starCount: number;
starsGiven: number;
followers: number;
following: number;
repositoriesContributedTo: number;
discussionsStarted: number;
discussionsAnswered: number;
totalContributions: number;
codeByteTotal: number;
topLanguages: Language[];
contributionStats: ContributionStats;
contributionsCollection: ContributionsCollection;
topRepos: RepoDetails[];
};
export interface GraphQLResponse {
user: User;
viewer: Viewer;
rateLimit?: RateLimitInfo;
}
export interface User {

251
src/index.test.ts Normal file
View File

@@ -0,0 +1,251 @@
import { describe, expect, test } from "bun:test";
import {
formatBytes,
formatNumber,
aggregateLanguages,
calculateContributionStats,
processBatched,
} from "./index";
import type { ContributionsCollection } from "./Types";
describe("formatBytes", () => {
test("formats bytes correctly", () => {
expect(formatBytes(0)).toBe("0 B");
expect(formatBytes(512)).toBe("512 B");
expect(formatBytes(1023)).toBe("1023 B");
});
test("formats kilobytes correctly", () => {
expect(formatBytes(1024)).toBe("1.0 KB");
expect(formatBytes(1536)).toBe("1.5 KB");
expect(formatBytes(10240)).toBe("10.0 KB");
});
test("formats megabytes correctly", () => {
expect(formatBytes(1024 * 1024)).toBe("1.0 MB");
expect(formatBytes(1.5 * 1024 * 1024)).toBe("1.5 MB");
expect(formatBytes(100 * 1024 * 1024)).toBe("100.0 MB");
});
test("formats gigabytes correctly", () => {
expect(formatBytes(1024 * 1024 * 1024)).toBe("1.0 GB");
expect(formatBytes(2.5 * 1024 * 1024 * 1024)).toBe("2.5 GB");
});
});
describe("formatNumber", () => {
test("formats small numbers as-is", () => {
expect(formatNumber(0)).toBe("0");
expect(formatNumber(1)).toBe("1");
expect(formatNumber(999)).toBe("999");
});
test("formats thousands with K suffix", () => {
expect(formatNumber(1000)).toBe("1.0K");
expect(formatNumber(1500)).toBe("1.5K");
expect(formatNumber(10000)).toBe("10.0K");
expect(formatNumber(999999)).toBe("1000.0K");
});
test("formats millions with M suffix", () => {
expect(formatNumber(1000000)).toBe("1.0M");
expect(formatNumber(1500000)).toBe("1.5M");
expect(formatNumber(10000000)).toBe("10.0M");
});
});
describe("aggregateLanguages", () => {
test("aggregates languages from multiple repos", () => {
const repos = [
{
languages: {
edges: [
{ size: 1000, node: { name: "TypeScript", color: "#3178c6" } },
{ size: 500, node: { name: "JavaScript", color: "#f1e05a" } },
],
},
},
{
languages: {
edges: [
{ size: 2000, node: { name: "TypeScript", color: "#3178c6" } },
{ size: 300, node: { name: "Python", color: "#3572A5" } },
],
},
},
];
const result = aggregateLanguages(repos);
expect(result.codeByteTotal).toBe(3800);
expect(result.languages).toHaveLength(3);
// Should be sorted by value descending
expect(result.languages[0].languageName).toBe("TypeScript");
expect(result.languages[0].value).toBe(3000);
expect(result.languages[0].percentage).toBeCloseTo(78.95, 1);
expect(result.languages[1].languageName).toBe("JavaScript");
expect(result.languages[1].value).toBe(500);
expect(result.languages[2].languageName).toBe("Python");
expect(result.languages[2].value).toBe(300);
});
test("handles empty repos", () => {
const repos: Array<{ languages: { edges: Array<{ size: number; node: { name: string; color: string } }> } }> = [];
const result = aggregateLanguages(repos);
expect(result.codeByteTotal).toBe(0);
expect(result.languages).toHaveLength(0);
});
test("handles repos with no languages", () => {
const repos = [{ languages: { edges: [] } }];
const result = aggregateLanguages(repos);
expect(result.codeByteTotal).toBe(0);
expect(result.languages).toHaveLength(0);
});
test("preserves language colors", () => {
const repos = [
{
languages: {
edges: [{ size: 1000, node: { name: "Rust", color: "#dea584" } }],
},
},
];
const result = aggregateLanguages(repos);
expect(result.languages[0].color).toBe("#dea584");
});
});
describe("calculateContributionStats", () => {
const createContributionsCollection = (
days: Array<{ date: string; contributionCount: number }>
): ContributionsCollection => ({
totalCommitContributions: 0,
restrictedContributionsCount: 0,
totalIssueContributions: 0,
totalRepositoryContributions: 0,
totalPullRequestContributions: 0,
totalPullRequestReviewContributions: 0,
contributionCalendar: {
totalContributions: days.reduce((sum, d) => sum + d.contributionCount, 0),
weeks: [{ contributionDays: days }],
},
});
test("calculates longest streak correctly", () => {
const collection = createContributionsCollection([
{ date: "2024-01-01", contributionCount: 1 },
{ date: "2024-01-02", contributionCount: 2 },
{ date: "2024-01-03", contributionCount: 0 },
{ date: "2024-01-04", contributionCount: 1 },
{ date: "2024-01-05", contributionCount: 1 },
{ date: "2024-01-06", contributionCount: 1 },
]);
const stats = calculateContributionStats(collection);
expect(stats.longestStreak).toBe(3);
});
test("calculates averages correctly", () => {
const collection = createContributionsCollection([
{ date: "2024-01-01", contributionCount: 10 },
{ date: "2024-01-02", contributionCount: 20 },
{ date: "2024-01-03", contributionCount: 30 },
{ date: "2024-01-04", contributionCount: 40 },
]);
const stats = calculateContributionStats(collection);
expect(stats.averagePerDay).toBe(25);
expect(stats.averagePerWeek).toBe(175);
});
test("calculates monthly breakdown correctly", () => {
const collection = createContributionsCollection([
{ date: "2024-01-15", contributionCount: 5 },
{ date: "2024-01-20", contributionCount: 10 },
{ date: "2024-02-10", contributionCount: 15 },
]);
const stats = calculateContributionStats(collection);
expect(stats.monthlyBreakdown).toHaveLength(2);
expect(stats.monthlyBreakdown[0]).toEqual({ month: "2024-01", contributions: 15 });
expect(stats.monthlyBreakdown[1]).toEqual({ month: "2024-02", contributions: 15 });
});
test("identifies most active day of week", () => {
// Create contributions heavily weighted toward Monday
const collection = createContributionsCollection([
{ date: "2024-01-01", contributionCount: 100 }, // Monday
{ date: "2024-01-02", contributionCount: 1 }, // Tuesday
{ date: "2024-01-08", contributionCount: 100 }, // Monday
{ date: "2024-01-09", contributionCount: 1 }, // Tuesday
]);
const stats = calculateContributionStats(collection);
expect(stats.mostActiveDay).toBe("Monday");
});
test("handles empty contribution data", () => {
const collection = createContributionsCollection([]);
const stats = calculateContributionStats(collection);
expect(stats.longestStreak).toBe(0);
expect(stats.currentStreak).toBe(0);
expect(stats.averagePerDay).toBe(0);
expect(stats.monthlyBreakdown).toHaveLength(0);
});
});
describe("processBatched", () => {
test("processes items in batches", async () => {
const items = [1, 2, 3, 4, 5];
const processedOrder: number[] = [];
const results = await processBatched(items, 2, async (item) => {
processedOrder.push(item);
return item * 2;
});
expect(results).toHaveLength(5);
expect(results.filter((r) => r.status === "fulfilled")).toHaveLength(5);
const values = results
.filter((r): r is PromiseFulfilledResult<number> => r.status === "fulfilled")
.map((r) => r.value);
expect(values).toEqual([2, 4, 6, 8, 10]);
});
test("handles errors gracefully with Promise.allSettled", async () => {
const items = [1, 2, 3];
const results = await processBatched(items, 2, async (item) => {
if (item === 2) throw new Error("Test error");
return item;
});
expect(results).toHaveLength(3);
expect(results[0].status).toBe("fulfilled");
expect(results[1].status).toBe("rejected");
expect(results[2].status).toBe("fulfilled");
});
test("processes single batch correctly", async () => {
const items = [1, 2, 3];
const results = await processBatched(items, 10, async (item) => item);
expect(results).toHaveLength(3);
});
test("handles empty array", async () => {
const results = await processBatched([], 5, async (item: number) => item);
expect(results).toHaveLength(0);
});
});

View File

@@ -1,23 +1,80 @@
import core from "@actions/core";
import { config } from "dotenv";
import { writeFileSync } from "fs";
import { Octokit } from "octokit";
import { throttling } from "@octokit/plugin-throttling";
import { ContributionsCollection, Language } from "./Types";
import {
ContributionsCollection,
Language,
RateLimitInfo,
ContributionStats,
MonthlyContribution,
RepoDetails,
} from "./Types";
import type { GraphQlQueryResponseData } from "@octokit/graphql";
config();
const ThrottledOctokit = Octokit.plugin(throttling);
// Constants
const MAX_RETRY_COUNT = 5;
const BATCH_SIZE = 10;
const RETRY_DELAY_MS = 2000;
/**
* Log rate limit information from GraphQL response
*/
function logRateLimit(rateLimit: RateLimitInfo | undefined, context: string) {
if (rateLimit) {
console.log(
`[Rate Limit] ${context}: ${rateLimit.remaining}/${rateLimit.limit} remaining (resets at ${rateLimit.resetAt})`
);
}
}
/**
* Process promises in batches to avoid overwhelming the API
*/
export async function processBatched<T, R>(
items: T[],
batchSize: number,
processor: (item: T) => Promise<R>
): Promise<PromiseSettledResult<R>[]> {
const results: PromiseSettledResult<R>[] = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.allSettled(batch.map(processor));
results.push(...batchResults);
}
return results;
}
/**
* Get user profile data with extended fields
*/
export async function getUserData(
octokit: Octokit,
username: string
): Promise<GraphQlQueryResponseData> {
return octokit.graphql(
const response = await octokit.graphql<GraphQlQueryResponseData & { rateLimit: RateLimitInfo }>(
`query userInfo($login: String!) {
user(login: $login) {
name
login
bio
company
location
email
twitterUsername
websiteUrl
avatarUrl
createdAt
followers {
totalCount
}
following {
totalCount
}
pullRequests(first: 1) {
totalCount
}
@@ -30,9 +87,6 @@ export async function getUserData(
closedIssues: issues(states: CLOSED) {
totalCount
}
followers {
totalCount
}
repositoryDiscussions {
totalCount
}
@@ -51,13 +105,19 @@ export async function getUserData(
login: username,
}
);
logRateLimit(response.rateLimit, "getUserData");
return response;
}
/**
* Get repository data with extended metadata
*/
export async function getRepoData(
octokit: Octokit,
username: string
): Promise<GraphQlQueryResponseData> {
return octokit.graphql.paginate(
const response = await octokit.graphql.paginate(
`query repoInfo($login: String!, $cursor: String) {
user(login: $login) {
repositories(
@@ -68,11 +128,19 @@ export async function getRepoData(
) {
totalCount
nodes {
name
description
isArchived
createdAt
updatedAt
stargazers {
totalCount
}
forkCount
primaryLanguage {
name
color
}
languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {
edges {
size
@@ -89,34 +157,45 @@ export async function getRepoData(
}
}
}
rateLimit {
limit
remaining
used
resetAt
}
}`,
{
login: username,
}
);
logRateLimit((response as { rateLimit?: RateLimitInfo }).rateLimit, "getRepoData");
return response as GraphQlQueryResponseData;
}
/**
* Get contribution collection for a date range using proper GraphQL variables
*/
export async function getContributionCollection(
octokit: Octokit,
year: string
createdAt: string
) {
const yearCreated = new Date(year);
const yearCreated = new Date(createdAt);
const currentYear = new Date();
const promises = [];
for (let i = yearCreated.getFullYear(); i <= currentYear.getFullYear(); i++) {
let startYear = `${i}-01-01T00:00:00.000Z`;
if (i === yearCreated.getFullYear()) startYear = year;
if (i === yearCreated.getFullYear()) startYear = createdAt;
let endYear = `${i + 1}-01-01T00:00:00.000Z`;
if (i === currentYear.getFullYear()) endYear = currentYear.toISOString();
promises.push(
octokit
.graphql<
Promise<{
.graphql<{
viewer: { contributionsCollection: ContributionsCollection };
}>
>(
`{
rateLimit: RateLimitInfo;
}>(
`query getContributions($from: DateTime!, $to: DateTime!) {
rateLimit {
limit
remaining
@@ -124,14 +203,10 @@ export async function getContributionCollection(
resetAt
}
viewer {
contributionsCollection(
from: "${startYear}"
to: "${endYear}"
) {
contributionsCollection(from: $from, to: $to) {
totalCommitContributions
restrictedContributionsCount
totalIssueContributions
totalCommitContributions
totalRepositoryContributions
totalPullRequestContributions
totalPullRequestReviewContributions
@@ -146,18 +221,31 @@ export async function getContributionCollection(
}
}
}
}`,
{
from: startYear,
to: endYear,
}
`
)
.then((response) => {
logRateLimit(response.rateLimit, `getContributionCollection year ${i}`);
return response;
})
.catch((error) => {
console.error(`Failed to fetch data for year ${i}: ${error.message}`);
return null;
})
);
}
const years = (await Promise.all(promises)).filter(Boolean) as {
const years = (await Promise.allSettled(promises))
.filter(
(result): result is PromiseFulfilledResult<{
viewer: { contributionsCollection: ContributionsCollection };
}[];
rateLimit: RateLimitInfo;
} | null> => result.status === "fulfilled" && result.value !== null
)
.map((result) => result.value!);
if (years.length === 0) {
throw new Error("Failed to fetch data for all years");
@@ -195,75 +283,255 @@ export async function getContributionCollection(
return contributionsCollection;
}
/**
* Get total commits for user
*/
export async function getTotalCommits(octokit: Octokit, username: string) {
return octokit.rest.search.commits({
q: `author:${username}`,
});
}
/**
* Get stars given by user
*/
export async function getUsersStars(octokit: Octokit, username: string) {
return octokit.rest.activity.listReposStarredByUser({
const response = await octokit.rest.activity.listReposStarredByUser({
username,
per_page: 1,
});
// The total count is in the Link header or we can make a request with per_page=1
// and check the last page. For simplicity, we'll count from headers if available.
const linkHeader = response.headers.link;
if (linkHeader) {
const lastPageMatch = linkHeader.match(/page=(\d+)>; rel="last"/);
if (lastPageMatch) {
return parseInt(lastPageMatch[1], 10);
}
}
return response.data.length;
}
/**
* Get contributor stats for a repo with retry limit
*/
export async function getReposContributorsStats(
octokit: Octokit,
username: string,
repo: string
) {
owner: string,
repo: string,
retryCount = 0
): Promise<Awaited<ReturnType<typeof octokit.rest.repos.getContributorsStats>> | undefined> {
try {
const response = await octokit.rest.repos.getContributorsStats({
owner: username,
owner,
repo,
});
if (response.status === 202) {
// Retry after the specified delay
await new Promise((resolve) => setTimeout(resolve, 2 * 1000));
// Retry the request
return getReposContributorsStats(octokit, username, repo);
} else {
return response;
if (retryCount >= MAX_RETRY_COUNT) {
console.warn(
`Max retries (${MAX_RETRY_COUNT}) reached for ${owner}/${repo}, skipping`
);
return undefined;
}
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
return getReposContributorsStats(octokit, owner, repo, retryCount + 1);
}
return response;
} catch (error) {
console.error(error);
console.error(`Error fetching contributor stats for ${owner}/${repo}:`, error);
return undefined;
}
}
/**
* Get view count for a repo
*/
export async function getReposViewCount(
octokit: Octokit,
username: string,
owner: string,
repo: string
) {
return octokit.rest.repos.getViews({
per: "week",
owner: username,
owner,
repo,
});
}
export const NOT_LANGUAGES = [
"html",
"markdown",
"dockerfile",
"roff",
"rich text format",
"powershell",
"css",
"php",
];
/**
* Calculate contribution statistics from calendar data
*/
export function calculateContributionStats(
contributionsCollection: ContributionsCollection
): ContributionStats {
const allDays: { date: string; count: number }[] = [];
const monthlyMap = new Map<string, number>();
const dayOfWeekCounts = new Map<string, number>();
const NOT_LANGUAGES_OBJ = Object.fromEntries(
NOT_LANGUAGES.map((l) => [l, true])
// Flatten all contribution days
for (const week of contributionsCollection.contributionCalendar.weeks) {
for (const day of week.contributionDays) {
allDays.push({ date: day.date, count: day.contributionCount });
// Monthly aggregation
const month = day.date.substring(0, 7); // YYYY-MM
monthlyMap.set(month, (monthlyMap.get(month) || 0) + day.contributionCount);
// Day of week aggregation
const dayOfWeek = new Date(day.date).toLocaleDateString("en-US", {
weekday: "long",
});
dayOfWeekCounts.set(
dayOfWeek,
(dayOfWeekCounts.get(dayOfWeek) || 0) + day.contributionCount
);
}
}
try {
// Sort days by date
allDays.sort((a, b) => a.date.localeCompare(b.date));
// Calculate streaks
let currentStreak = 0;
let longestStreak = 0;
let tempStreak = 0;
const today = new Date().toISOString().split("T")[0];
const yesterday = new Date(Date.now() - 86400000).toISOString().split("T")[0];
for (let i = allDays.length - 1; i >= 0; i--) {
const day = allDays[i];
if (day.count > 0) {
tempStreak++;
if (i === allDays.length - 1 && (day.date === today || day.date === yesterday)) {
currentStreak = tempStreak;
}
} else {
longestStreak = Math.max(longestStreak, tempStreak);
tempStreak = 0;
}
}
longestStreak = Math.max(longestStreak, tempStreak);
// Recalculate current streak from the end
currentStreak = 0;
for (let i = allDays.length - 1; i >= 0; i--) {
if (allDays[i].count > 0) {
currentStreak++;
} else if (allDays[i].date !== today) {
// Allow today to have 0 if we're still on today
break;
}
}
// Find most active day
let mostActiveDay = "Sunday";
let maxDayCount = 0;
for (const [day, count] of dayOfWeekCounts) {
if (count > maxDayCount) {
maxDayCount = count;
mostActiveDay = day;
}
}
// Calculate averages
const totalDays = allDays.length || 1;
const totalContributions =
contributionsCollection.contributionCalendar.totalContributions;
const averagePerDay = totalContributions / totalDays;
const averagePerWeek = averagePerDay * 7;
const averagePerMonth = averagePerDay * 30;
// Monthly breakdown sorted by date
const monthlyBreakdown: MonthlyContribution[] = Array.from(monthlyMap.entries())
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([month, contributions]) => ({ month, contributions }));
return {
longestStreak,
currentStreak,
mostActiveDay,
averagePerDay: Math.round(averagePerDay * 100) / 100,
averagePerWeek: Math.round(averagePerWeek * 100) / 100,
averagePerMonth: Math.round(averagePerMonth * 100) / 100,
monthlyBreakdown,
};
}
/**
* Aggregate languages from repos using Map for O(n) performance
*/
export function aggregateLanguages(
repos: Array<{
languages: { edges: Array<{ size: number; node: { name: string; color: string } }> };
}>
): { languages: Language[]; codeByteTotal: number } {
const languageMap = new Map<string, { color: string | null; value: number }>();
let codeByteTotal = 0;
for (const repo of repos) {
for (const edge of repo.languages.edges) {
const langName = edge.node.name;
const existing = languageMap.get(langName);
if (existing) {
existing.value += edge.size;
} else {
languageMap.set(langName, {
color: edge.node.color,
value: edge.size,
});
}
codeByteTotal += edge.size;
}
}
// Convert to array, sort by value descending, add percentages
const languages: Language[] = Array.from(languageMap.entries())
.map(([languageName, data]) => ({
languageName,
color: data.color,
value: data.value,
percentage: codeByteTotal > 0
? Math.round((data.value / codeByteTotal) * 10000) / 100
: 0,
}))
.sort((a, b) => b.value - a.value);
return { languages, codeByteTotal };
}
/**
* Format bytes to human-readable string
*/
export function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
/**
* Format number to human-readable string
*/
export function formatNumber(num: number): string {
if (num < 1000) return num.toString();
if (num < 1000000) return `${(num / 1000).toFixed(1)}K`;
return `${(num / 1000000).toFixed(1)}M`;
}
/**
* Main function
*/
async function main() {
const setup1 = performance.now();
const token = process.env["GITHUB_TOKEN"];
if (!token) core.error("GITHUB_TOKEN is not present");
if (!token) {
core.setFailed("GITHUB_TOKEN is not present");
return;
}
const octokit = new ThrottledOctokit({
auth: token,
@@ -274,14 +542,12 @@ try {
);
if (retryCount < 1) {
// only retries once
octokit.log.info(`Retrying after ${retryAfter} seconds!`);
return true;
}
return false;
},
onSecondaryRateLimit: (retryAfter, options, octokit) => {
// does not retry, only logs a warning
octokit.log.warn(
`SecondaryRateLimit detected for request ${options.method} ${options.url}.`
);
@@ -292,41 +558,57 @@ try {
});
const fetchedAt = Date.now();
const setup2 = performance.now();
console.log(`Setup time: ${(setup2 - setup1).toFixed(2)}ms`);
console.log(`Setup time: ${setup2 - setup1}ms`);
// Fetch main data in parallel
const main1 = performance.now();
const userDetails = await octokit.rest.users.getAuthenticated();
const username = userDetails.data.login;
const [userData, repoData, totalCommits, contributionsCollection] =
const [userData, repoData, totalCommits, contributionsCollection, starsGiven] =
await Promise.all([
getUserData(octokit, username),
getRepoData(octokit, username),
getTotalCommits(octokit, username),
getContributionCollection(octokit, userDetails.data.created_at),
getUsersStars(octokit, username),
]);
const main2 = performance.now();
console.log(`Main data fetch time: ${(main2 - main1).toFixed(2)}ms`);
console.log(`Main time: ${main2 - main1}ms`);
const viewCountPromises = [];
// Process repos
const repos = repoData.user.repositories.nodes;
let starCount = 0;
let forkCount = 0;
let contribStatsPromises = [];
let contributorStats = [];
const repos = repoData.user.repositories.nodes;
interface RepoInfo {
owner: string;
name: string;
isOwner: boolean;
stars: number;
forks: number;
description: string | null;
isArchived: boolean;
primaryLanguage: string | null;
updatedAt: string;
createdAt: string;
}
const promisesCreate1 = performance.now();
const promisesResolve1 = performance.now();
for (const repo of repos) {
let repoOwner, repoName;
const repoInfoList: RepoInfo[] = repos.map((repo: {
name: string;
nameWithOwner?: string;
stargazers: { totalCount: number };
forkCount: number;
description: string | null;
isArchived: boolean;
primaryLanguage: { name: string } | null;
updatedAt: string;
createdAt: string;
}) => {
let repoOwner: string;
let repoName: string;
if (repo.nameWithOwner) {
[repoOwner, repoName] = repo.nameWithOwner.split("/");
@@ -335,201 +617,212 @@ try {
repoName = repo.name;
}
contribStatsPromises.push(
getReposContributorsStats(octokit, repoOwner, repoName)
);
if (repoOwner === username) {
viewCountPromises.push(getReposViewCount(octokit, username, repoName));
const isOwner = repoOwner === username;
if (isOwner) {
starCount += repo.stargazers.totalCount;
forkCount += repo.forkCount;
}
}
const promisesCreate2 = performance.now();
return {
owner: repoOwner,
name: repoName,
isOwner,
stars: repo.stargazers.totalCount,
forks: repo.forkCount,
description: repo.description,
isArchived: repo.isArchived,
primaryLanguage: repo.primaryLanguage?.name || null,
updatedAt: repo.updatedAt,
createdAt: repo.createdAt,
};
});
console.log(`Promises create time: ${promisesCreate2 - promisesCreate1}ms`);
const repoContribStatsResps = await Promise.all(contribStatsPromises);
const promisesResolve2 = performance.now();
console.log(
`Promises resolve time: ${promisesResolve2 - promisesResolve1}ms`
// Fetch contributor stats in batches
const contribStats1 = performance.now();
const contribStatsResults = await processBatched(
repoInfoList,
BATCH_SIZE,
(repo) => getReposContributorsStats(octokit, repo.owner, repo.name)
);
const contribStats2 = performance.now();
console.log(`Contributor stats fetch time: ${(contribStats2 - contribStats1).toFixed(2)}ms`);
const parseRepoPromises1 = performance.now();
// Fetch view counts in batches (only for owned repos)
const viewCount1 = performance.now();
const ownedRepos = repoInfoList.filter((r) => r.isOwner);
const viewCountResults = await processBatched(
ownedRepos,
BATCH_SIZE,
(repo) => getReposViewCount(octokit, repo.owner, repo.name)
);
const viewCount2 = performance.now();
console.log(`View count fetch time: ${(viewCount2 - viewCount1).toFixed(2)}ms`);
for (const resp of repoContribStatsResps) {
if (!resp) {
continue;
}
// Process contributor stats
const parseStats1 = performance.now();
let linesAdded = 0;
let linesDeleted = 0;
let commitCount = 0;
let stats;
for (const result of contribStatsResults) {
if (result.status !== "fulfilled" || !result.value) continue;
if (!Array.isArray(resp.data)) {
console.log(resp);
stats = [resp.data];
} else {
stats = resp.data;
}
const repoContribStats = stats.find(
const resp = result.value;
const stats = Array.isArray(resp.data) ? resp.data : [resp.data];
const userStats = stats.find(
(contributor) => contributor?.author?.login === username
);
if (repoContribStats?.weeks)
contributorStats.push(...repoContribStats.weeks);
if (userStats?.weeks) {
for (const week of userStats.weeks) {
if (week.a) linesAdded += week.a;
if (week.d) linesDeleted += week.d;
if (week.c) commitCount += week.c;
}
const parseRepoPromises2 = performance.now();
console.log(
`Parse repo promises time: ${parseRepoPromises2 - parseRepoPromises1}ms`
);
const parseLines1 = performance.now();
let linesOfCodeChanged = 0;
let addedLines = 0;
let deletedLines = 0;
let changedLines = 0;
for (const week of contributorStats) {
if (week.a) {
linesOfCodeChanged += week.a;
addedLines += week.a;
}
if (week.d) {
linesOfCodeChanged += week.d;
deletedLines += week.d;
}
if (week.c) {
linesOfCodeChanged += week.c;
changedLines += week.c;
}
}
const parseLines2 = performance.now();
console.log(`Parse lines time: ${parseLines2 - parseLines1}ms`);
const linesOfCodeChanged = linesAdded + linesDeleted;
const parseStats2 = performance.now();
console.log(`Parse contributor stats time: ${(parseStats2 - parseStats1).toFixed(2)}ms`);
// Process view counts
const parseViews1 = performance.now();
const viewCounts = await Promise.all(viewCountPromises);
let repoViews = 0;
for (const viewCount of viewCounts) {
repoViews += viewCount.data.count;
for (const result of viewCountResults) {
if (result.status === "fulfilled" && result.value) {
repoViews += result.value.data.count;
}
}
const parseViews2 = performance.now();
console.log(`Parse views time: ${(parseViews2 - parseViews1).toFixed(2)}ms`);
console.log(`Parse views time: ${parseViews2 - parseViews1}ms`);
// Aggregate languages with O(n) performance
const parseLang1 = performance.now();
const topLanguages: Language[] = [];
let codeByteTotal = 0;
for (const node of repoData.user.repositories.nodes) {
for (const edge of node.languages.edges) {
if (NOT_LANGUAGES_OBJ[edge.node.name.toLowerCase()]) {
continue;
}
const existingLanguage = topLanguages.find(
(l) => l.languageName === edge.node.name
);
if (existingLanguage) {
existingLanguage.value += edge.size;
codeByteTotal += edge.size;
} else {
topLanguages.push({
languageName: edge.node.name,
color: edge.node.color,
value: edge.size,
});
codeByteTotal += edge.size;
}
}
}
const { languages: topLanguages, codeByteTotal } = aggregateLanguages(repos);
const parseLang2 = performance.now();
console.log(`Parse languages time: ${(parseLang2 - parseLang1).toFixed(2)}ms`);
console.log(`Parse languages time: ${parseLang2 - parseLang1}ms`);
// Calculate contribution statistics
const calcStats1 = performance.now();
const contributionStats = calculateContributionStats(contributionsCollection);
const calcStats2 = performance.now();
console.log(`Calculate contribution stats time: ${(calcStats2 - calcStats1).toFixed(2)}ms`);
// Build top repos list (top 10 by stars)
const topRepos: RepoDetails[] = repoInfoList
.filter((r) => r.isOwner && !r.isArchived)
.sort((a, b) => b.stars - a.stars)
.slice(0, 10)
.map((r) => ({
name: r.name,
description: r.description,
stars: r.stars,
forks: r.forks,
isArchived: r.isArchived,
primaryLanguage: r.primaryLanguage,
updatedAt: r.updatedAt,
createdAt: r.createdAt,
}));
// Build output
const tableData = [
["Name", userDetails.data.name || ""],
["Username", username],
["Repository Views", repoViews],
["Lines of Code Changed", linesOfCodeChanged],
["Lines Added", addedLines],
["Lines Deleted", deletedLines],
["Lines Changed", changedLines],
["Total Commits", totalCommits.data.total_count],
["Repository Views", formatNumber(repoViews)],
["Lines of Code Changed", formatNumber(linesOfCodeChanged)],
["Lines Added", formatNumber(linesAdded)],
["Lines Deleted", formatNumber(linesDeleted)],
["Commit Count (from stats)", formatNumber(commitCount)],
["Total Commits (search)", formatNumber(totalCommits.data.total_count)],
["Total Pull Requests", userData.user.pullRequests.totalCount],
["Code Byte Total", codeByteTotal],
["Top Languages", topLanguages.map((lang) => lang.languageName).join(", ")],
["Total PR Reviews", contributionsCollection.totalPullRequestReviewContributions],
["Code Bytes Total", formatBytes(codeByteTotal)],
["Top Languages", topLanguages.slice(0, 5).map((lang) => lang.languageName).join(", ")],
["Fork Count", forkCount],
["Star Count", starCount],
[
"Total Contributions",
contributionsCollection.contributionCalendar.totalContributions,
],
["Stars Given", starsGiven],
["Followers", userData.user.followers.totalCount],
["Following", userData.user.following.totalCount],
["Current Streak", `${contributionStats.currentStreak} days`],
["Longest Streak", `${contributionStats.longestStreak} days`],
["Most Active Day", contributionStats.mostActiveDay],
["Total Contributions", contributionsCollection.contributionCalendar.totalContributions],
["Closed Issues", userData.user.closedIssues.totalCount],
["Open Issues", userData.user.openIssues.totalCount],
["Fetched At", fetchedAt],
["Fetched At", new Date(fetchedAt).toISOString()],
];
const formattedTableData = tableData.map((row) => {
return { Name: row[0], Value: row[1] };
});
const formattedTableData = tableData.map((row) => ({
Name: row[0],
Value: row[1],
}));
console.table(formattedTableData);
writeFileSync(
"github-user-stats.json",
JSON.stringify(
{
// Write output file
const output = {
name: userDetails.data.name || "",
avatarUrl: userDetails.data.avatar_url,
username,
bio: userData.user.bio || null,
company: userData.user.company || null,
location: userData.user.location || null,
email: userData.user.email || null,
twitterUsername: userData.user.twitterUsername || null,
websiteUrl: userData.user.websiteUrl || null,
createdAt: userDetails.data.created_at,
repoViews,
linesOfCodeChanged,
linesAdded: addedLines,
linesDeleted: deletedLines,
linesChanged: changedLines,
linesAdded,
linesDeleted,
commitCount,
totalCommits: totalCommits.data.total_count,
totalPullRequests: userData.user.pullRequests.totalCount,
totalPullRequestReviews: contributionsCollection.totalPullRequestReviewContributions,
codeByteTotal,
topLanguages,
forkCount,
starCount,
totalContributions:
contributionsCollection.contributionCalendar.totalContributions,
starsGiven,
followers: userData.user.followers.totalCount,
following: userData.user.following.totalCount,
repositoriesContributedTo: userData.user.repositoriesContributedTo.totalCount,
discussionsStarted: userData.user.repositoryDiscussions.totalCount,
discussionsAnswered: userData.user.repositoryDiscussionComments.totalCount,
totalContributions: contributionsCollection.contributionCalendar.totalContributions,
contributionStats,
contributionsCollection,
topRepos,
closedIssues: userData.user.closedIssues.totalCount,
openIssues: userData.user.openIssues.totalCount,
fetchedAt,
},
null,
4
)
);
};
if (process.env["GITHUB_WORKFLOW"])
writeFileSync("github-user-stats.json", JSON.stringify(output, null, 2));
// Write GitHub Actions summary
if (process.env["GITHUB_WORKFLOW"]) {
await core.summary
.addHeading("Test Results")
.addHeading("GitHub Stats")
.addTable([
[
{ data: "Name", header: true },
{ data: "Metric", header: true },
{ data: "Value", header: true },
],
...tableData.map((row) => [String(row[0]), String(row[1])]),
])
.write();
} catch (error) {
core.setFailed(error as string);
}
console.log(`\nTotal execution time: ${(performance.now() - setup1).toFixed(2)}ms`);
}
// Run main function only when this file is the entry point
const isMainModule = import.meta.main ?? process.argv[1]?.endsWith("index.ts");
if (isMainModule) {
main().catch((error) => {
console.error("Fatal error:", error);
core.setFailed(error instanceof Error ? error.message : String(error));
});
}

View File

@@ -1,8 +0,0 @@
import { describe, expect, it } from "vitest";
import { sum } from "../src/sum";
describe("add", () => {
it("should sum of 2 and 3 equals to 5", () => {
expect(sum(2, 3)).toEqual(5);
});
});

View File

@@ -2,25 +2,22 @@
"compilerOptions": {
"rootDir": "src",
"outDir": "lib",
"paths": {
"@/*": ["./src/*"],
"@@/*": ["./*"]
},
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"moduleResolution": "bundler",
"lib": ["ESNext"],
"types": ["bun"],
"strict": true,
"sourceMap": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true,
"types": ["node"]
"forceConsistentCasingInFileNames": true
},
"include": ["src"],
"exclude": ["**/*.test.ts", "node_modules", "test/**", ".history/**"]
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}

538
yarn.lock
View File

@@ -1,538 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@actions/core@^1.10.1":
version "1.10.1"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.10.1.tgz#61108e7ac40acae95ee36da074fa5850ca4ced8a"
integrity sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==
dependencies:
"@actions/http-client" "^2.0.1"
uuid "^8.3.2"
"@actions/github@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@actions/github/-/github-6.0.0.tgz#65883433f9d81521b782a64cc1fd45eef2191ea7"
integrity sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==
dependencies:
"@actions/http-client" "^2.2.0"
"@octokit/core" "^5.0.1"
"@octokit/plugin-paginate-rest" "^9.0.0"
"@octokit/plugin-rest-endpoint-methods" "^10.0.0"
"@actions/http-client@^2.0.1", "@actions/http-client@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@actions/http-client/-/http-client-2.2.0.tgz#f8239f375be6185fcd07765efdcf0031ad5df1a0"
integrity sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==
dependencies:
tunnel "^0.0.6"
undici "^5.25.4"
"@fastify/busboy@^2.0.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff"
integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==
"@octokit/action@^6.0.7":
version "6.0.7"
resolved "https://registry.yarnpkg.com/@octokit/action/-/action-6.0.7.tgz#7a0d6f19439e955a9aca8f03fed8ca07bccd5999"
integrity sha512-0Q1L96F8JsNb+M2NzN7r4artGyX02Pa9tzg+JaxXncvdHEXVIJFnkA8CstC52EB4DAZ0b8wpqDOG05v/DcyS3g==
dependencies:
"@octokit/auth-action" "^4.0.0"
"@octokit/core" "^5.0.0"
"@octokit/plugin-paginate-rest" "^9.0.0"
"@octokit/plugin-rest-endpoint-methods" "^10.0.0"
"@octokit/types" "^12.0.0"
undici "^6.0.0"
"@octokit/app@^14.0.2":
version "14.0.2"
resolved "https://registry.yarnpkg.com/@octokit/app/-/app-14.0.2.tgz#b47c52020221351fb58640f113eb38b2ad3998fe"
integrity sha512-NCSCktSx+XmjuSUVn2dLfqQ9WIYePGP95SDJs4I9cn/0ZkeXcPkaoCLl64Us3dRKL2ozC7hArwze5Eu+/qt1tg==
dependencies:
"@octokit/auth-app" "^6.0.0"
"@octokit/auth-unauthenticated" "^5.0.0"
"@octokit/core" "^5.0.0"
"@octokit/oauth-app" "^6.0.0"
"@octokit/plugin-paginate-rest" "^9.0.0"
"@octokit/types" "^12.0.0"
"@octokit/webhooks" "^12.0.4"
"@octokit/auth-action@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-action/-/auth-action-4.0.1.tgz#b6fe7579b5ee1d544accdb9588c7697681a93fa6"
integrity sha512-mJLOcFFafIivLZ7BEkGDCTFoHPJv7BeL5Zwy7j5qMDU0b/DKshhi6GCU9tw3vmKhOxTNquYfvwqsEfPpemaaxg==
dependencies:
"@octokit/auth-token" "^4.0.0"
"@octokit/types" "^12.0.0"
"@octokit/auth-app@^6.0.0":
version "6.0.4"
resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-6.0.4.tgz#3c435c4c9ba9005405d889f4a5018df5e8ca93cf"
integrity sha512-TPmJYgd05ok3nzHj7Y6we/V7Ez1wU3ztLFW3zo/afgYFtqYZg0W7zb6Kp5ag6E85r8nCE1JfS6YZoZusa14o9g==
dependencies:
"@octokit/auth-oauth-app" "^7.0.0"
"@octokit/auth-oauth-user" "^4.0.0"
"@octokit/request" "^8.0.2"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
deprecation "^2.3.1"
lru-cache "^10.0.0"
universal-github-app-jwt "^1.1.2"
universal-user-agent "^6.0.0"
"@octokit/auth-oauth-app@^7.0.0":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz#30fd8fcb4608ca52c29c265a3fc7032897796c8e"
integrity sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==
dependencies:
"@octokit/auth-oauth-device" "^6.0.0"
"@octokit/auth-oauth-user" "^4.0.0"
"@octokit/request" "^8.0.2"
"@octokit/types" "^12.0.0"
"@types/btoa-lite" "^1.0.0"
btoa-lite "^1.0.0"
universal-user-agent "^6.0.0"
"@octokit/auth-oauth-device@^6.0.0":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz#38e5f7f8997c5e8b774f283463ecf4a7e42d7cee"
integrity sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==
dependencies:
"@octokit/oauth-methods" "^4.0.0"
"@octokit/request" "^8.0.0"
"@octokit/types" "^12.0.0"
universal-user-agent "^6.0.0"
"@octokit/auth-oauth-user@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz#c8267883935c83f78318c726ff91d7e98de05517"
integrity sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==
dependencies:
"@octokit/auth-oauth-device" "^6.0.0"
"@octokit/oauth-methods" "^4.0.0"
"@octokit/request" "^8.0.2"
"@octokit/types" "^12.0.0"
btoa-lite "^1.0.0"
universal-user-agent "^6.0.0"
"@octokit/auth-token@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-4.0.0.tgz#40d203ea827b9f17f42a29c6afb93b7745ef80c7"
integrity sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==
"@octokit/auth-unauthenticated@^5.0.0":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz#d8032211728333068b2e07b53997c29e59a03507"
integrity sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==
dependencies:
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
"@octokit/core@^5.0.0", "@octokit/core@^5.0.1":
version "5.1.0"
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.1.0.tgz#81dacf0197ed7855e6413f128bd6dd9e121e7d2f"
integrity sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==
dependencies:
"@octokit/auth-token" "^4.0.0"
"@octokit/graphql" "^7.0.0"
"@octokit/request" "^8.0.2"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
before-after-hook "^2.2.0"
universal-user-agent "^6.0.0"
"@octokit/endpoint@^9.0.0":
version "9.0.4"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.4.tgz#8afda5ad1ffc3073d08f2b450964c610b821d1ea"
integrity sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==
dependencies:
"@octokit/types" "^12.0.0"
universal-user-agent "^6.0.0"
"@octokit/graphql@^7.0.0":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-7.0.2.tgz#3df14b9968192f9060d94ed9e3aa9780a76e7f99"
integrity sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==
dependencies:
"@octokit/request" "^8.0.1"
"@octokit/types" "^12.0.0"
universal-user-agent "^6.0.0"
"@octokit/oauth-app@^6.0.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@octokit/oauth-app/-/oauth-app-6.1.0.tgz#22c276f6ad2364c6999837bfdd5d9c1092838726"
integrity sha512-nIn/8eUJ/BKUVzxUXd5vpzl1rwaVxMyYbQkNZjHrF7Vk/yu98/YDF/N2KeWO7uZ0g3b5EyiFXFkZI8rJ+DH1/g==
dependencies:
"@octokit/auth-oauth-app" "^7.0.0"
"@octokit/auth-oauth-user" "^4.0.0"
"@octokit/auth-unauthenticated" "^5.0.0"
"@octokit/core" "^5.0.0"
"@octokit/oauth-authorization-url" "^6.0.2"
"@octokit/oauth-methods" "^4.0.0"
"@types/aws-lambda" "^8.10.83"
universal-user-agent "^6.0.0"
"@octokit/oauth-authorization-url@^6.0.2":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz#cc82ca29cc5e339c9921672f39f2b3f5c8eb6ef2"
integrity sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==
"@octokit/oauth-methods@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-4.0.1.tgz#90d22c662387056307778d7e5c4763ff559636c4"
integrity sha512-1NdTGCoBHyD6J0n2WGXg9+yDLZrRNZ0moTEex/LSPr49m530WNKcCfXDghofYptr3st3eTii+EHoG5k/o+vbtw==
dependencies:
"@octokit/oauth-authorization-url" "^6.0.2"
"@octokit/request" "^8.0.2"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
btoa-lite "^1.0.0"
"@octokit/openapi-types@^20.0.0":
version "20.0.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-20.0.0.tgz#9ec2daa0090eeb865ee147636e0c00f73790c6e5"
integrity sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==
"@octokit/plugin-paginate-graphql@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.0.tgz#b26024fa454039c18b948f13bf754ff86b89e8b9"
integrity sha512-7HcYW5tP7/Z6AETAPU14gp5H5KmCPT3hmJrS/5tO7HIgbwenYmgw4OY9Ma54FDySuxMwD+wsJlxtuGWwuZuItA==
"@octokit/plugin-paginate-rest@^9.0.0":
version "9.2.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.0.tgz#ca08e32adfab72a1223c4f4b77c9f0222087f879"
integrity sha512-NKi0bJEZqOSbBLMv9kdAcuocpe05Q2xAXNLTGi0HN2GSMFJHNZuSoPNa0tcQFTOFCKe+ZaYBZ3lpXh1yxgUDCA==
dependencies:
"@octokit/types" "^12.6.0"
"@octokit/plugin-rest-endpoint-methods@^10.0.0":
version "10.4.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.0.tgz#f3756b080b75c397e82052de0105535b5d830ead"
integrity sha512-INw5rGXWlbv/p/VvQL63dhlXr38qYTHkQ5bANi9xofrF9OraqmjHsIGyenmjmul1JVRHpUlw5heFOj1UZLEolA==
dependencies:
"@octokit/types" "^12.6.0"
"@octokit/plugin-retry@^6.0.0":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz#3257404f7cc418e1c1f13a7f2012c1db848b7693"
integrity sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==
dependencies:
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
bottleneck "^2.15.3"
"@octokit/plugin-throttling@^8.0.0":
version "8.2.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz#9ec3ea2e37b92fac63f06911d0c8141b46dc4941"
integrity sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==
dependencies:
"@octokit/types" "^12.2.0"
bottleneck "^2.15.3"
"@octokit/request-error@^5.0.0":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.1.tgz#277e3ce3b540b41525e07ba24c5ef5e868a72db9"
integrity sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==
dependencies:
"@octokit/types" "^12.0.0"
deprecation "^2.0.0"
once "^1.4.0"
"@octokit/request@^8.0.0", "@octokit/request@^8.0.1", "@octokit/request@^8.0.2":
version "8.2.0"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.2.0.tgz#125c547bc3f4c0e2dfa38c6829a1cf00027fbd98"
integrity sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==
dependencies:
"@octokit/endpoint" "^9.0.0"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
universal-user-agent "^6.0.0"
"@octokit/types@^12.0.0", "@octokit/types@^12.2.0", "@octokit/types@^12.6.0":
version "12.6.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-12.6.0.tgz#8100fb9eeedfe083aae66473bd97b15b62aedcb2"
integrity sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==
dependencies:
"@octokit/openapi-types" "^20.0.0"
"@octokit/webhooks-methods@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@octokit/webhooks-methods/-/webhooks-methods-4.1.0.tgz#681a6c86c9b21d4ec9e29108fb053ae7512be033"
integrity sha512-zoQyKw8h9STNPqtm28UGOYFE7O6D4Il8VJwhAtMHFt2C4L0VQT1qGKLeefUOqHNs1mNRYSadVv7x0z8U2yyeWQ==
"@octokit/webhooks-types@7.3.2":
version "7.3.2"
resolved "https://registry.yarnpkg.com/@octokit/webhooks-types/-/webhooks-types-7.3.2.tgz#c4a5049b28a6d4b0c397f4db48a9112fef579ba0"
integrity sha512-JWOoOgtWTFnTSAamPXXyjTY5/apttvNxF+vPBnwdSu5cj5snrd7FO0fyw4+wTXy8fHduq626JjhO+TwCyyA6vA==
"@octokit/webhooks@^12.0.4":
version "12.1.2"
resolved "https://registry.yarnpkg.com/@octokit/webhooks/-/webhooks-12.1.2.tgz#df4c5623d608c11b3b2e6aaa2b531b5f27755357"
integrity sha512-+nGS3ReCByF6m+nbNB59x7Aa3CNjCCGuBLFzfkiJP1O3uVKKuJbkP4uO4t46YqH26nlugmOhqjT7nx5D0VPtdA==
dependencies:
"@octokit/request-error" "^5.0.0"
"@octokit/webhooks-methods" "^4.1.0"
"@octokit/webhooks-types" "7.3.2"
aggregate-error "^3.1.0"
"@types/aws-lambda@^8.10.83":
version "8.10.134"
resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.134.tgz#8f65d86736839889194f7892b7bec6b8a7ec6fc3"
integrity sha512-cfv422ivDMO+EeA3N4YcshbTHBL+5lLXe+Uz+4HXvIcsCuWvqNFpOs28ZprL8NA3qRCzt95ETiNAJDn4IcC/PA==
"@types/btoa-lite@^1.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/btoa-lite/-/btoa-lite-1.0.2.tgz#82bb6aab00abf7cff3ca2825abe010c0cd536ae5"
integrity sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==
"@types/jsonwebtoken@^9.0.0":
version "9.0.6"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3"
integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@^20.11.20":
version "20.11.20"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.20.tgz#f0a2aee575215149a62784210ad88b3a34843659"
integrity sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==
dependencies:
undici-types "~5.26.4"
"@vercel/ncc@^0.38.1":
version "0.38.1"
resolved "https://registry.yarnpkg.com/@vercel/ncc/-/ncc-0.38.1.tgz#13f08738111e1d9e8a22fd6141f3590e54d9a60e"
integrity sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==
aggregate-error@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==
dependencies:
clean-stack "^2.0.0"
indent-string "^4.0.0"
before-after-hook@^2.2.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c"
integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==
bottleneck@^2.15.3:
version "2.19.5"
resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91"
integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==
btoa-lite@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337"
integrity sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
deprecation@^2.0.0, deprecation@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
dotenv@^16.4.5:
version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
jsonwebtoken@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^7.5.4"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lru-cache@^10.0.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
lru-cache@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
octokit@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/octokit/-/octokit-3.1.2.tgz#e574e4f2f5f8712e10412ce81fb56a74c93d4cfa"
integrity sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==
dependencies:
"@octokit/app" "^14.0.2"
"@octokit/core" "^5.0.0"
"@octokit/oauth-app" "^6.0.0"
"@octokit/plugin-paginate-graphql" "^4.0.0"
"@octokit/plugin-paginate-rest" "^9.0.0"
"@octokit/plugin-rest-endpoint-methods" "^10.0.0"
"@octokit/plugin-retry" "^6.0.0"
"@octokit/plugin-throttling" "^8.0.0"
"@octokit/request-error" "^5.0.0"
"@octokit/types" "^12.0.0"
once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
semver@^7.5.4:
version "7.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
dependencies:
lru-cache "^6.0.0"
tunnel@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c"
integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
typescript@^5.3.3:
version "5.3.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici@^5.25.4:
version "5.28.3"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b"
integrity sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==
dependencies:
"@fastify/busboy" "^2.0.0"
undici@^6.0.0:
version "6.6.2"
resolved "https://registry.yarnpkg.com/undici/-/undici-6.6.2.tgz#8dce5ae54e8a3bc7140c2b2a0972b5fde9a88efb"
integrity sha512-vSqvUE5skSxQJ5sztTZ/CdeJb1Wq0Hf44hlYMciqHghvz+K88U0l7D6u1VsndoFgskDcnU+nG3gYmMzJVzd9Qg==
dependencies:
"@fastify/busboy" "^2.0.0"
universal-github-app-jwt@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-1.1.2.tgz#8c1867a394d7d9d42cda34f11d1bcb023797d8df"
integrity sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA==
dependencies:
"@types/jsonwebtoken" "^9.0.0"
jsonwebtoken "^9.0.2"
universal-user-agent@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa"
integrity sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==