Compare commits

..
Author SHA1 Message Date
Taehyun Hwangandrickstaa c624fe9507 Add types for common directory (#2153)
* Add types for common directory

* Reflect the code reviews

* style: improve syntax

* style: improve typescript syntax

Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-16 10:33:33 +02:00
Taehyun Hwang bcda6ce01e Add createProgressNode type (#2109) 2022-10-04 14:00:11 +02:00
rickstaa af041ba9b8 Merge branch 'master' into ts_migration_base 2022-10-04 13:18:43 +02:00
rickstaa a7d3aaa7b4 test: fix e2e test config path 2022-10-04 13:16:33 +02:00
Matteo Pierroandrickstaa af97e5765b fix: fetch all repos for for the stats card (#2100)
* fetch all stars

* stop fetching when there are repos with zero stars

* remove not needed parameters from the query

* add docstring

* removed not needed mock

* style: update formatting

Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-04 13:10:50 +02:00
rickstaa 1617c3fb22 feat: add initial typescript migration
This commit performs the base changes that are needed to migrate the
codebase to typescript.
2022-10-04 11:43:31 +02:00
nekiwoandrickstaa acbc03dc0f Fix card length during error (#2105)
* Fix card length during error

Fixes #1774
The size of the box should be 550px to allow 25px padding from both sides
I found the width of the text using `getComputedTextLength()` method

* fix: improve error card size

Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-04 10:00:48 +02:00
Akshat Goelandrickstaa 388ba06a8f Update the Vercel deployement guide (#2102)
* Update the Vercel deployement guide

Updated Readme.md - The guide on deploying your own Vercel instance

* docs: fix some small grammer errors

Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-04 09:32:40 +02:00
Rick Staa e6a6384eff docs: improve readme syntax (#2106) 2022-10-04 09:19:37 +02:00
Aditya Shelkeandrickstaa 7accd1a8d7 feat: default values for wakatime and language (#2103)
* default values for wakatime and language

* docs: update formatting

Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-04 08:58:18 +02:00
c03bb2f250 fix: adding docstrings to the files where it was missing (#2101)
* fix: adding docstrings to missing files

* style: format code

* style: improve formatting

Co-authored-by: Jagruti Tiwari <jagrutit@cdac.in>
Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-03 13:03:23 +02:00
56 changed files with 5805 additions and 1159 deletions
+5 -5
View File
@@ -1,15 +1,15 @@
import * as dotenv from "dotenv";
import { renderStatsCard } from "../src/cards/stats-card.js";
import { blacklist } from "../src/common/blacklist.js";
import { renderStatsCard } from "../src/cards/stats-card";
import { blacklist } from "../src/common/blacklist";
import {
clampValue,
CONSTANTS,
parseArray,
parseBoolean,
renderError,
} from "../src/common/utils.js";
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
} from "../src/common/utils";
import { fetchStats } from "../src/fetchers/stats-fetcher";
import { isLocaleAvailable } from "../src/translations";
dotenv.config();
+5 -5
View File
@@ -1,13 +1,13 @@
import { renderRepoCard } from "../src/cards/repo-card.js";
import { blacklist } from "../src/common/blacklist.js";
import { renderRepoCard } from "../src/cards/repo-card";
import { blacklist } from "../src/common/blacklist";
import {
clampValue,
CONSTANTS,
parseBoolean,
renderError,
} from "../src/common/utils.js";
import { fetchRepo } from "../src/fetchers/repo-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
} from "../src/common/utils";
import { fetchRepo } from "../src/fetchers/repo-fetcher";
import { isLocaleAvailable } from "../src/translations";
export default async (req, res) => {
const {
+5 -5
View File
@@ -1,15 +1,15 @@
import * as dotenv from "dotenv";
import { renderTopLanguages } from "../src/cards/top-languages-card.js";
import { blacklist } from "../src/common/blacklist.js";
import { renderTopLanguages } from "../src/cards/top-languages-card";
import { blacklist } from "../src/common/blacklist";
import {
clampValue,
CONSTANTS,
parseArray,
parseBoolean,
renderError,
} from "../src/common/utils.js";
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
} from "../src/common/utils";
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher";
import { isLocaleAvailable } from "../src/translations";
dotenv.config();
+4 -4
View File
@@ -1,14 +1,14 @@
import * as dotenv from "dotenv";
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
import { renderWakatimeCard } from "../src/cards/wakatime-card";
import {
clampValue,
CONSTANTS,
parseArray,
parseBoolean,
renderError,
} from "../src/common/utils.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
} from "../src/common/utils";
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher";
import { isLocaleAvailable } from "../src/translations";
dotenv.config();
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
],
};
+4 -4
View File
@@ -1,8 +1,8 @@
export default {
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
clearMocks: true,
transform: {},
testEnvironment: "jsdom",
coverageProvider: "v8",
testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/tests/e2e/"],
modulePathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/tests/e2e/"],
coveragePathIgnorePatterns: [
+7
View File
@@ -0,0 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
clearMocks: true,
testMatch: ["<rootDir>/tests/e2e/**/*.test.ts"],
};
-7
View File
@@ -1,7 +0,0 @@
export default {
clearMocks: true,
transform: {},
testEnvironment: "node",
coverageProvider: "v8",
testMatch: ["<rootDir>/tests/e2e/**/*.test.js"],
};
+4917 -666
View File
File diff suppressed because it is too large Load Diff
+16 -7
View File
@@ -5,35 +5,44 @@
"main": "index.js",
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
"test:update:snapshot": "node --experimental-vm-modules node_modules/jest/bin/jest.js -u",
"test:e2e": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.e2e.config.js",
"test": "jest --coverage",
"test:watch": "jest --watch",
"test:update:snapshot": "jest -u",
"test:e2e": "jest --config jest.e2e.config.cjs",
"theme-readme-gen": "node scripts/generate-theme-doc",
"preview-theme": "node scripts/preview-theme",
"close-stale-theme-prs": "node scripts/close-stale-theme-prs",
"generate-langs-json": "node scripts/generate-langs-json",
"format": "./node_modules/.bin/prettier --write .",
"format:check": "./node_modules/.bin/prettier --check ."
"format:check": "./node_modules/.bin/prettier --check .",
"start": "vercel dev"
},
"author": "Anurag Hazra",
"license": "MIT",
"devDependencies": {
"@actions/core": "^1.9.1",
"@actions/github": "^4.0.0",
"@babel/core": "^7.19.3",
"@babel/preset-env": "^7.19.3",
"@babel/preset-typescript": "^7.18.6",
"@testing-library/dom": "^8.17.1",
"@testing-library/jest-dom": "^5.16.5",
"@types/jest": "^29.1.1",
"@uppercod/css-to-object": "^1.1.1",
"@vercel/node": "^2.5.21",
"axios-mock-adapter": "^1.18.1",
"babel-jest": "^29.1.2",
"color-contrast-checker": "^2.1.0",
"hjson": "^3.2.2",
"husky": "^4.2.5",
"jest": "^29.0.3",
"jest": "^29.1.2",
"jest-environment-jsdom": "^29.0.3",
"js-yaml": "^4.1.0",
"lodash.snakecase": "^4.1.1",
"parse-diff": "^0.7.0",
"prettier": "^2.1.2"
"prettier": "^2.1.2",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4"
},
"dependencies": {
"axios": "^0.24.0",
+61 -63
View File
@@ -6,7 +6,7 @@
<p align="center">
<a href="https://github.com/anuraghazra/github-readme-stats/actions">
<img alt="Tests Passing" src="https://github.com/anuraghazra/github-readme-stats/workflows/Test/badge.svg" />
</a>
</a>
<a href="https://github.com/anuraghazra/github-readme-stats/graphs/contributors">
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/anuraghazra/github-readme-stats" />
</a>
@@ -64,18 +64,16 @@
</p>
<p align="center">Love the project? Please consider <a href="https://www.paypal.me/anuraghazra">donating</a> to help it improve!
<p>
<a href="https://indiafightscorona.giveindia.org">
<img src="https://indiaspora.org/wp-content/uploads/2021/04/give-India-logo.png" alt="Give india logo" width="200" />
<img src="https://indiaspora.org/wp-content/uploads/2021/04/give-India-logo.png" alt="Give india logo" width="200" />
</a>
Are you considering supporting the project by donating? Please DON'T!!
Are you considering supporting the project by donating? Please DO NOT!!
Instead, Help India fight the 2nd deadly wave of COVID-19.
Instead, Help India fight the second deadly wave of COVID-19.
Thousands of people are dying in India because of a lack of Oxygen & also COVID-related infrastructure.
Visit <https://indiafightscorona.giveindia.org> and make a small donation to help us fight COVID and overcome this crisis.
A small donation goes a long way. :heart:
Visit <https://indiafightscorona.giveindia.org> and make a small donation to help us fight COVID and overcome this crisis. A small donation goes a long way. :heart:
</p>
@@ -96,7 +94,7 @@ A small donation goes a long way. :heart:
# GitHub Stats Card
Copy-paste this into your markdown content, and that's it. Simple!
Copy-paste this into your markdown content, and that is it. Simple!
Change the `?username=` value to your GitHub username.
@@ -110,7 +108,7 @@ The implementation can be investigated at [src/calculateRank.js](./src/calculate
### Hiding individual stats
To hide any specific stats, you can pass a query parameter `&hide=` with comma-separated values.
You can pass a query parameter `&hide=` to hide any specific stats with comma-separated values.
> Options: `&hide=stars,commits,prs,issues,contribs`
@@ -122,7 +120,7 @@ To hide any specific stats, you can pass a query parameter `&hide=` with comma-s
You can add the count of all your private contributions to the total commits count by using the query parameter `&count_private=true`.
_Note: If you are deploying this project yourself, the private contributions will be counted by default. Otherwise, you need to choose to share your private contribution counts._
_Note: If you are deploying this project yourself, the private contributions will be counted by default. If you are using the public Vercel instance, you need to choose to [share your private contributions](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/managing-contribution-settings-on-your-profile/showing-your-private-contributions-and-achievements-on-your-profile)._
> Options: `&count_private=true`
@@ -142,15 +140,15 @@ To enable icons, you can pass `show_icons=true` in the query param, like so:
With inbuilt themes, you can customize the look of the card without doing any [manual customization](#customization).
Use `&theme=THEME_NAME` parameter like so :-
Use `&theme=THEME_NAME` parameter like so :
```md
![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical)
```
#### All inbuilt themes:-
#### All inbuilt themes
dark, radical, merko, gruvbox, tokyonight, onedark, cobalt, synthwave, highcontrast, dracula
Github readme stats comes with several built-in themes (e.g. `dark`, `radical`, `merko`, `gruvbox`, `tokyonight`, `onedark`, `cobalt`, `synthwave`, `highcontrast`, `dracula`).
<img src="https://res.cloudinary.com/anuraghazra/image/upload/v1595174536/grs-themes_l4ynja.png" alt="GitHub Readme Stats Themes" width="600px"/>
@@ -158,9 +156,9 @@ You can look at a preview for [all available themes](./themes/README.md) or chec
### Customization
You can customize the appearance of your `Stats Card` or `Repo Card` however you wish with URL params.
You can customize the appearance of your `Stats Card` or `Repo Card` however you wish with URL parameters.
#### Common Options:
#### Common Options
- `title_color` - Card's title color _(hex color)_. Default: `2f80ed`.
- `text_color` - Body text color _(hex color)_. Default: `434d58`.
@@ -177,13 +175,13 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
##### Gradient in bg_color
You can provide multiple comma-separated values in the bg_color option to render a gradient, with the following format:
You can provide multiple comma-separated values in the bg_color option to render a gradient with the following format:
&bg_color=DEG,COLOR1,COLOR2,COLOR3...COLOR10
> Note on cache: Repo cards have a default cache of 4 hours (14400 seconds) if the fork count & star count is less than 1k, otherwise, it's 2 hours (7200 seconds). Also, note that the cache is clamped to a minimum of 2 hours and a maximum of 24 hours.
> Note on cache: Repo cards have a default cache of 4 hours (14400 seconds) if the fork count & star count is less than 1k; otherwise, it is 2 hours (7200 seconds). Also, note that the cache is clamped to a minimum of 2 hours and a maximum of 24 hours.
#### Stats Card Exclusive Options:
#### Stats Card Exclusive Options
- `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_. Default: `[] (blank array)`.
- `hide_title` - _(boolean)_. Default: `false`.
@@ -192,7 +190,7 @@ You can provide multiple comma-separated values in the bg_color option to render
- `show_icons` - _(boolean)_. Default: `false`.
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
- `count_private` - Count private commits _(boolean)_. Default: `false`.
- `line_height` - Sets the line-height between text _(number)_. Default: `25`.
- `line_height` - Sets the line height between text _(number)_. Default: `25`.
- `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
- `custom_title` - Sets a custom title for the card. Default: `<username> Github Stats`.
- `text_bold` - Use bold text _(boolean)_. Default: `true`.
@@ -201,42 +199,42 @@ You can provide multiple comma-separated values in the bg_color option to render
> Note on `hide_rank`:
> When hide_rank=`true`, the minimum card width is 270 px + the title length and padding.
#### Repo Card Exclusive Options:
#### Repo Card Exclusive Options
- `show_owner` - Show the repo's owner name _(boolean)_. Defaults to `false`.
- `show_owner` - Show the repo's owner name _(boolean)_. Default: `false`.
#### Language Card Exclusive Options:
#### Language Card Exclusive Options
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
- `hide_title` - _(boolean)_. Defaults to `false`.
- `layout` - Switch between two available layouts `default` & `compact`
- `card_width` - Set the card's width manually _(number)_
- `langs_count` - Show more languages on the card, between 1-10, defaults to 5 _(number)_
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_
- `custom_title` - Sets a custom title for the card
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
- `hide_title` - _(boolean)_. Default: `false`.
- `layout` - Switch between two available layouts `default` & `compact`. Default: `default`.
- `card_width` - Set the card's width manually _(number)_. Default `300`.
- `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`.
- `exclude_repo` - Exclude specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
- `custom_title` - Sets a custom title for the card _(string)_. Default `Most Used Languages`.
> :warning: **Important:**
> Language names should be uri-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
> (i.e: `c++` should become `c%2B%2B`, `jupyter notebook` should become `jupyter%20notebook`, etc.) You can use
> [urlencoder.org](https://www.urlencoder.org/) to help you do this automatically.
#### Wakatime Card Exclusive Options:
#### Wakatime Card Exclusive Options
- `hide` - Hide the languages specified from the card _(Comma-separated values)_
- `hide_title` - _(boolean)_. Defaults to `false`.
- `line_height` - Sets the line-height between text _(number)_. Default Value: `25`.
- `hide_progress` - Hides the progress bar and percentage _(boolean)_
- `custom_title` - Sets a custom title for the card
- `layout` - Switch between two available layouts `default` & `compact`
- `langs_count` - Limit the number of languages on the card, defaults to all reported languages
- `api_domain` - Set a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi)
- `range` Request a range different from your WakaTime default, e.g. `last_7_days`. See [WakaTime API docs](https://wakatime.com/developers#stats) for a list of available options.
- `hide` - Hide the languages specified from the card _(Comma-separated values)_. Default: `[] (blank array)`.
- `hide_title` - _(boolean)_. Default `false`.
- `line_height` - Sets the line height between text _(number)_. Default `25`.
- `hide_progress` - Hides the progress bar and percentage _(boolean)_. Default `false`.
- `custom_title` - Sets a custom title for the card _(string)_. Default `Wakatime Stats`.
- `layout` - Switch between two available layouts `default` & `compact`. Default `default`.
- `langs_count` - Limit the number of languages on the card, defaults to all reported languages _(number)_.
- `api_domain` - Set a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) _(string)_. Default `Waka API`.
- `range` Request a range different from your WakaTime default, e.g. `last_7_days`. See [WakaTime API docs](https://wakatime.com/developers#stats) for a list of available options. _(YYYY-MM, last_7_days, last_30_days, last_6_months, last_year, or all_time)_. Default `all_time`.
* * *
# GitHub Extra Pins
GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile.
GitHub extra pins allow you to pin more than six repositories in your profile using a GitHub readme profile.
Yay! You are no longer limited to 6 pinned repositories.
@@ -262,7 +260,7 @@ Use [show_owner](#customization) variable to include the repo's owner username
The top languages card shows a GitHub user's most frequently used top language.
_NOTE: Top Languages does not indicate my skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It's a new feature of github-readme-stats._
_NOTE: Top Languages does not indicate my skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats._
### Usage
@@ -276,7 +274,7 @@ Endpoint: `api/top-langs?username=anuraghazra`
### Exclude individual repositories
You can use `&exclude_repo=repo1,repo2` parameter to exclude individual repositories.
You can use the `&exclude_repo=repo1,repo2` parameter to exclude individual repositories.
```md
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&exclude_repo=github-readme-stats,anuraghazra.github.io)](https://github.com/anuraghazra/github-readme-stats)
@@ -384,7 +382,7 @@ Choose from any of the [default themes](#themes)
[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats)
- Wakatime card
- WakaTime card
[![willianrod's wakatime stats](https://github-readme-stats.vercel.app/api/wakatime?username=willianrod)](https://github.com/anuraghazra/github-readme-stats)
@@ -392,7 +390,7 @@ Choose from any of the [default themes](#themes)
### Quick Tip (Align The Repo Cards)
You usually won't be able to layout the images side by side. To do that you can use this approach:
By default, GitHub does not lay out the cards side by side. To do that, you can use this approach:
```html
<a href="https://github.com/anuraghazra/github-readme-stats">
@@ -407,7 +405,7 @@ You usually won't be able to layout the images side by side. To do that you can
#### [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107)
Since the GitHub API only allows 5k requests per hour, my `https://github-readme-stats.vercel.app/api` could possibly hit the rate limiter. If you host it on your own Vercel server, then you don't have to worry about anything. Click on the deploy button to get started!
Since the GitHub API only allows 5k requests per hour, my `https://github-readme-stats.vercel.app/api` could possibly hit the rate limiter. If you host it on your own Vercel server, then you do not have to worry about anything. Click on the deploy button to get started!
NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we should be able to handle more than 5k requests and have no issues with downtime :D
@@ -416,32 +414,32 @@ NOTE: Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58) we
<details>
<summary><b> Guide on setting up Vercel 🔨 </b></summary>
1. Go to [vercel.com](https://vercel.com/)
2. Click on `Log in`
![](https://files.catbox.moe/tct1wg.png)
3. Sign in with GitHub by pressing `Continue with GitHub`
![](https://files.catbox.moe/btd78j.jpeg)
4. Sign in to GitHub and allow access to all repositories, if prompted
5. Fork this repo
6. After forking the repo, open the [`vercel.json`](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json#L5) file and change the `maxDuration` field to `10`
7. Go back to your [Vercel dashboard](https://vercel.com/dashboard)
8. Select `Import Project`
![](https://files.catbox.moe/qckos0.png)
9. Select `Import Git Repository`. Select root and keep everything as is.
![](https://files.catbox.moe/pqub9q.png)
10. Create a personal access token (PAT) [here](https://github.com/settings/tokens/new) and enable the `repo` permissions (this allows access to see private repo stats)
1. Go to [vercel.com](https://vercel.com/).
2. Click on `Log in`.
![](https://files.catbox.moe/pcxk33.png)
3. Sign in with GitHub by pressing `Continue with GitHub`.
![](https://files.catbox.moe/b9oxey.png)
4. Sign in to GitHub and allow access to all repositories if prompted.
5. Fork this repo.
6. After forking the repo, open the [`vercel.json`](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json#L5) file and change the `maxDuration` field to `10`.
7. Go back to your [Vercel dashboard](https://vercel.com/dashboard).
8. To import a project, click the `Add New...` button and select the `Project` option.
![](https://files.catbox.moe/3n76fh.png)
9. Click the `Continue with GitHub` button, search for the required Git Repository and import it by clicking the `Import` button. Alternatively, you can import a Third-Party Git Repository using the `Import Third-Party Git Repository ->` link at the bottom of the page.
![](https://files.catbox.moe/mg5p04.png)
10. Create a personal access token (PAT) [here](https://github.com/settings/tokens/new) and enable the `repo` permissions (this allows access to see private repo stats).
11. Add the PAT as an environment variable named `PAT_1` (as shown).
![](https://files.catbox.moe/0ez4g7.png)
![](https://files.catbox.moe/0yclio.png)
12. Click deploy, and you're good to go. See your domains to use the API!
</details>
## :sparkling_heart: Support the project
I open-source almost everything I can, and I try to reply to everyone needing help using these projects. Obviously,
I open-source almost everything I can and try to reply to everyone needing help using these projects. Obviously,
this takes time. You can use this service for free.
However, if you are using this project and are happy with it or just want to encourage me to continue creating stuff, there are a few ways you can do it:-
However, if you are using this project and are happy with it or just want to encourage me to continue creating stuff, there are a few ways you can do it:
- Giving proper credit when you use github-readme-stats on your readme, linking back to it :D
- Starring and sharing the project :rocket:
+23 -1
View File
@@ -1,4 +1,14 @@
// https://stackoverflow.com/a/5263759/10629172
/**
* Calculates the probability of x taking on x or a value less than x in a normal distribution
* with mean and standard deviation.
*
* @see https://stackoverflow.com/a/5263759/10629172
*
* @param {string} mean
* @param {number} sigma
* @param {number} to
* @returns {number} Probability.
*/
function normalcdf(mean, sigma, to) {
var z = (to - mean) / Math.sqrt(2 * sigma * sigma);
var t = 1 / (1 + 0.3275911 * Math.abs(z));
@@ -16,6 +26,18 @@ function normalcdf(mean, sigma, to) {
return (1 / 2) * (1 + sign * erf);
}
/**
* Calculates the users rank.
*
* @param {number} totalRepos
* @param {number} totalCommits
* @param {number} contributions
* @param {number} followers
* @param {number} prs
* @param {number} issues
* @param {number} stargazers
* @returns {{level: string, score: number}}} The users rank.
*/
function calculateRank({
totalRepos,
totalCommits,
@@ -1,7 +1,7 @@
// @ts-check
import { Card } from "../common/Card.js";
import { I18n } from "../common/I18n.js";
import { icons } from "../common/icons.js";
import { Card } from "../common/Card";
import { I18n } from "../common/I18n";
import { icons } from "../common/icons";
import {
encodeHTML,
flexLayout,
@@ -10,8 +10,8 @@ import {
measureText,
parseEmojis,
wrapTextMultiline,
} from "../common/utils.js";
import { repoCardLocales } from "../translations.js";
} from "../common/utils";
import { repoCardLocales } from "../translations";
/**
* @param {string} label
@@ -1,17 +1,30 @@
// @ts-check
import { Card } from "../common/Card.js";
import { I18n } from "../common/I18n.js";
import { icons } from "../common/icons.js";
import { Card } from "../common/Card";
import { I18n } from "../common/I18n";
import { icons } from "../common/icons";
import {
clampValue,
flexLayout,
getCardColors,
kFormatter,
measureText,
} from "../common/utils.js";
import { getStyles } from "../getStyles.js";
import { statCardLocales } from "../translations.js";
} from "../common/utils";
import { getStyles } from "../getStyles";
import { statCardLocales } from "../translations";
/**
* Create a stats card text item.
*
* @param {object[]} createTextNodeParams Object that contains the createTextNode parameters.
* @param {string} createTextNodeParams.label The label to display.
* @param {string} createTextNodeParams.value The value to display.
* @param {string} createTextNodeParams.id The id of the stat.
* @param {number} createTextNodeParams.index The index of the stat.
* @param {boolean} createTextNodeParams.showIcons Whether to show icons.
* @param {number} createTextNodeParams.shiftValuePos Number of pixels the value has to be shifted to the right.
* @param {boolean} createTextNodeParams.bold Whether to bold the label.
* @returns
*/
const createTextNode = ({
icon,
label,
@@ -1,7 +1,7 @@
// @ts-check
import { Card } from "../common/Card.js";
import { createProgressNode } from "../common/createProgressNode.js";
import { I18n } from "../common/I18n.js";
import { Card } from "../common/Card";
import { createProgressNode } from "../common/createProgressNode";
import { I18n } from "../common/I18n";
import {
chunkArray,
clampValue,
@@ -9,8 +9,8 @@ import {
getCardColors,
lowercaseTrim,
measureText,
} from "../common/utils.js";
import { langCardLocales } from "../translations.js";
} from "../common/utils";
import { langCardLocales } from "../translations";
const DEFAULT_CARD_WIDTH = 300;
const MIN_CARD_WIDTH = 230;
@@ -205,7 +205,7 @@ const calculateNormalLayoutHeight = (totalLangs) => {
const useLanguages = (topLangs, hide, langs_count) => {
let langs = Object.values(topLangs);
let langsToHide = {};
let langsCount = clampValue(parseInt(langs_count), 1, 100);
let langsCount = clampValue(parseInt(langs_count), 1, 10);
// populate langsToHide map for quick lookup
// while filtering out
+1 -1
View File
@@ -1,4 +1,4 @@
type ThemeNames = keyof typeof import("../../themes/index.js");
type ThemeNames = keyof typeof import("../../themes/index");
export type CommonOptions = {
title_color: string;
@@ -1,26 +1,17 @@
// @ts-check
import { Card } from "../common/Card.js";
import { createProgressNode } from "../common/createProgressNode.js";
import { I18n } from "../common/I18n.js";
import { Card } from "../common/Card";
import { createProgressNode } from "../common/createProgressNode";
import { I18n } from "../common/I18n";
import {
clampValue,
flexLayout,
getCardColors,
lowercaseTrim,
} from "../common/utils.js";
import { getStyles } from "../getStyles.js";
import { wakatimeCardLocales } from "../translations.js";
} from "../common/utils";
import { getStyles } from "../getStyles";
import { wakatimeCardLocales } from "../translations";
/** Import language colors.
*
* @description Here we use the workaround found in
* https://stackoverflow.com/questions/66726365/how-should-i-import-json-in-node
* since vercel is using v16.14.0 which does not yet support json imports without the
* --experimental-json-modules flag.
*/
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const languageColors = require("../common/languageColors.json"); // now works
import languageColors from "../common/languageColors.json";
/**
* @param {{color: string, text: string}} param0
+83 -15
View File
@@ -1,7 +1,78 @@
import { getAnimations } from "../getStyles.js";
import { encodeHTML, flexLayout } from "./utils.js";
import { getAnimations } from "../getStyles";
import { encodeHTML, flexLayout } from "./utils";
class Card {
/** Card colors. */
export interface CardColors {
/** Title color. */
titleColor: string;
/** Text color. */
textColor: string;
/** Icon color. */
iconColor: string;
/** Background color. */
bgColor: string;
/** Border color. */
borderColor: string;
}
/** Accessibility label. */
interface AccessibilityLabel {
/** The label to display. */
title: string;
/** The value to display. */
desc: string;
}
/** Card properties. */
interface CardProps {
/** Card width. */
width: number;
/** Card height. */
height: number;
/** Card border radius. */
border_radius: number;
/** Card colors. */
colors: CardColors | {};
/** Card title. */
customTitle?: string;
/** Card default title. */
defaultTitle?: string;
/** Card title prefix icon. */
titlePrefixIcon?: string;
}
/**
* Card class.
*/
export class Card {
/** Card width. */
width: number;
/** Card height. */
height: number;
/** Whether the card border is hidden. */
hideBorder: boolean;
/** Whether the card title is hidden. */
hideTitle: boolean;
/** Border radius. */
border_radius: number;
/** Card colors. */
colors: CardColors | {};
/** Card title. */
title: string;
/** Card css. */
css: string;
/** Card x padding. */
paddingX: number;
/** Card y padding. */
paddingY: number;
/** Card title prefix icon. */
titlePrefixIcon?: string;
/** Whether the card is animated. */
animations: boolean;
/** Accessibility label title. */
a11yTitle: string;
/** Accessibility label description. */
a11yDesc: string;
/**
* @param {object} args
* @param {number?=} args.width
@@ -10,7 +81,7 @@ class Card {
* @param {string?=} args.customTitle
* @param {string?=} args.defaultTitle
* @param {string?=} args.titlePrefixIcon
* @param {ReturnType<import('../common/utils.js').getCardColors>?=} args.colors
* @param {ReturnType<import('./utils').getCardColors>?=} args.colors
*/
constructor({
width = 100,
@@ -20,7 +91,7 @@ class Card {
customTitle,
defaultTitle = "",
titlePrefixIcon,
}) {
}: CardProps) {
this.width = width;
this.height = height;
@@ -53,7 +124,7 @@ class Card {
/**
* @param {{title: string, desc: string}} prop
*/
setAccessibilityLabel({ title, desc }) {
setAccessibilityLabel({ title, desc }: AccessibilityLabel) {
this.a11yTitle = title;
this.a11yDesc = desc;
}
@@ -61,21 +132,21 @@ class Card {
/**
* @param {string} value
*/
setCSS(value) {
setCSS(value: string) {
this.css = value;
}
/**
* @param {boolean} value
*/
setHideBorder(value) {
setHideBorder(value: boolean) {
this.hideBorder = value;
}
/**
* @param {boolean} value
*/
setHideTitle(value) {
setHideTitle(value: boolean) {
this.hideTitle = value;
if (value) {
this.height -= 30;
@@ -85,7 +156,7 @@ class Card {
/**
* @param {string} text
*/
setTitle(text) {
setTitle(text: string) {
this.title = text;
}
@@ -137,7 +208,7 @@ class Card {
gradientTransform="rotate(${this.colors.bgColor[0]})"
gradientUnits="userSpaceOnUse"
>
${gradients.map((grad, index) => {
${gradients.map((grad: string, index: number) => {
let offset = (index * 100) / (gradients.length - 1);
return `<stop offset="${offset}%" stop-color="#${grad}" />`;
})}
@@ -150,7 +221,7 @@ class Card {
/**
* @param {string} body
*/
render(body) {
render(body: string) {
return `
<svg
width="${this.width}"
@@ -215,6 +286,3 @@ class Card {
`;
}
}
export { Card };
export default Card;
-22
View File
@@ -1,22 +0,0 @@
class I18n {
constructor({ locale, translations }) {
this.locale = locale;
this.translations = translations;
this.fallbackLocale = "en";
}
t(str) {
if (!this.translations[str]) {
throw new Error(`${str} Translation string not found`);
}
if (!this.translations[str][this.locale || this.fallbackLocale]) {
throw new Error(`${str} Translation locale not found`);
}
return this.translations[str][this.locale || this.fallbackLocale];
}
}
export { I18n };
export default I18n;
+37
View File
@@ -0,0 +1,37 @@
type Translations = Record<string, Record<string, string>>;
/**
* I18n translation class.
*/
export class I18n {
/** The language locale. */
locale?: string;
/** The translations object. */
translations: Translations;
/** The fallback language locale. */
fallbackLocale: string;
constructor({
locale,
translations,
}: {
locale?: string;
translations: Translations;
}) {
this.locale = locale;
this.translations = translations;
this.fallbackLocale = "en";
}
t(str: string) {
if (!this.translations[str]) {
throw new Error(`${str} Translation string not found`);
}
if (!this.translations[str][this.locale || this.fallbackLocale]) {
throw new Error(`${str} Translation locale not found`);
}
return this.translations[str][this.locale || this.fallbackLocale];
}
}
-4
View File
@@ -1,4 +0,0 @@
const blacklist = ["renovate-bot", "technote-space", "sw-yx"];
export { blacklist };
export default blacklist;
+2
View File
@@ -0,0 +1,2 @@
/** User blacklist. */
export const blacklist = ["renovate-bot", "technote-space", "sw-yx"];
@@ -1,4 +1,21 @@
import { clampValue } from "./utils.js";
import { clampValue } from "./utils";
/** CreateProgressNode properties. */
interface CreateProgressNodeProps {
/** Progress bar item x position. */
x: number;
/** Progress bar item y position.. */
y: number;
/** Progress bar item width. */
width: number;
/** Progress bar item color. */
color: string;
/** Progress value. */
progress: number;
/** Progress bar item background color. */
progressBarBackgroundColor: string;
}
const createProgressNode = ({
x,
@@ -7,7 +24,7 @@ const createProgressNode = ({
color,
progress,
progressBarBackgroundColor,
}) => {
}: CreateProgressNodeProps) => {
const progressPercentage = clampValue(progress, 2, 100);
return `
+4 -1
View File
@@ -1,4 +1,7 @@
const icons = {
export type Icons = {[key: string]: string};
const icons: Icons = {
star: `<path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/>`,
commits: `<path fill-rule="evenodd" d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"/>`,
prs: `<path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/>`,
-44
View File
@@ -1,44 +0,0 @@
import { CustomError, logger } from "./utils.js";
const retryer = async (fetcher, variables, retries = 0) => {
if (retries > 7) {
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
}
try {
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
let response = await fetcher(
variables,
process.env[`PAT_${retries + 1}`],
retries,
);
// prettier-ignore
const isRateExceeded = response.data.errors && response.data.errors[0].type === "RATE_LIMITED";
// if rate limit is hit increase the RETRIES and recursively call the retryer
// with username, and current RETRIES
if (isRateExceeded) {
logger.log(`PAT_${retries + 1} Failed`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
}
// finally return the response
return response;
} catch (err) {
// prettier-ignore
// also checking for bad credentials if any tokens gets invalidated
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
if (isBadCredential) {
logger.log(`PAT_${retries + 1} Failed`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
}
}
};
export { retryer };
export default retryer;
+73
View File
@@ -0,0 +1,73 @@
import axios, {
AxiosError,
AxiosPromise,
AxiosRequestHeaders,
AxiosResponse,
} from "axios";
import { CustomError, logger } from "./utils";
/** Data fetcher. */
type Fetcher = (
/** Fetcher variables. */
variables: AxiosRequestHeaders,
/** Authentication token. */
token: string,
/** The number of retries. */
retries?: number,
) => AxiosPromise<any>
/**
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
*
* @param {object[]} retryerParams Object that contains the createTextNode parameters.
* @param {object[]} retryerParams.fetcher The fetcher function.
* @param {object[]} retryerParams.variables Object with arguments to pass to the fetcher function.
* @param {number} retryerParams.retries How many times to retry.
* @returns Promise<retryer>
*/
export const retryer = async (
fetcher: Fetcher,
variables: AxiosRequestHeaders,
retries = 0,
): Promise<any> => {
if (retries > 7) {
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
}
try {
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
let response = await fetcher(
variables,
process.env[`PAT_${retries + 1}`] as string,
retries,
);
// prettier-ignore
const isRateExceeded = response.data.errors && response.data.errors[0].type === "RATE_LIMITED";
// if rate limit is hit increase the RETRIES and recursively call the retryer
// with username, and current RETRIES
if (isRateExceeded) {
logger.log(`PAT_${retries + 1} Failed`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
}
// finally return the response
return response;
} catch (err) {
if (axios.isAxiosError(err)) {
// prettier-ignore
// also checking for bad credentials if any tokens gets invalidated
const isBadCredential = err?.response?.data && err.response.data.message === "Bad credentials";
if (isBadCredential) {
logger.log(`PAT_${retries + 1} Failed`);
retries++;
// directly return from the function
return retryer(fetcher, variables, retries);
}
}
}
};
+74 -39
View File
@@ -1,23 +1,24 @@
// @ts-check
import axios from "axios";
import axios, { AxiosRequestConfig, AxiosRequestHeaders } from "axios";
import toEmoji from "emoji-name-map";
import wrap from "word-wrap";
import { themes } from "../../themes/index.js";
import { themes } from "../../themes/index";
import type { ThemeEnum } from "../../themes/index";
/**
* @param {string} message
* @param {string} secondaryMessage
* @returns {string}
*/
const renderError = (message, secondaryMessage = "") => {
const renderError = (message: string, secondaryMessage = "") => {
return `
<svg width="495" height="120" viewBox="0 0 495 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="576.5" height="120" viewBox="0 0 576.5 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.text { font: 600 16px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 }
.gray { fill: #858585 }
</style>
<rect x="0.5" y="0.5" width="494" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
<rect x="0.5" y="0.5" width="575.5" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
<text x="25" y="45" class="text">Something went wrong! file an issue at https://tiny.one/readme-stats</text>
<text data-testid="message" x="25" y="55" class="text small">
<tspan x="25" dy="18">${encodeHTML(message)}</tspan>
@@ -32,7 +33,7 @@ const renderError = (message, secondaryMessage = "") => {
* @param {string} str
* @returns {string}
*/
function encodeHTML(str) {
function encodeHTML(str: string) {
return str
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
return "&#" + i.charCodeAt(0) + ";";
@@ -43,7 +44,7 @@ function encodeHTML(str) {
/**
* @param {number} num
*/
function kFormatter(num) {
function kFormatter(num: number) {
return Math.abs(num) > 999
? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k"
: Math.sign(num) * Math.abs(num);
@@ -53,7 +54,7 @@ function kFormatter(num) {
* @param {string} hexColor
* @returns {boolean}
*/
function isValidHexColor(hexColor) {
function isValidHexColor(hexColor: string) {
return new RegExp(
/^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/,
).test(hexColor);
@@ -63,7 +64,7 @@ function isValidHexColor(hexColor) {
* @param {string} value
* @returns {boolean | string}
*/
function parseBoolean(value) {
function parseBoolean(value: string) {
if (value === "true") {
return true;
} else if (value === "false") {
@@ -74,28 +75,37 @@ function parseBoolean(value) {
}
/**
* @param {string} str
* Parse string to array of strings.
*
* @param {string} str The string to parse.
* @returns {string[]} The array of strings.
*/
function parseArray(str) {
function parseArray(str: string) {
if (!str) return [];
return str.split(",");
}
/**
* @param {number} number
* @param {number} min
* @param {number} max
* Clamp the given number between the given range.
*
* @param {number} number The number to clamp.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* returns {number} The clamped number.
*/
function clampValue(number, min, max) {
function clampValue(number: number, min: number, max: number) {
// @ts-ignore
if (Number.isNaN(parseInt(number))) return min;
return Math.max(min, Math.min(number, max));
}
/**
* @param {string[]} colors
* Check if the given string is a valid gradient.
*
* @param {string[]} colors Array of colors.
* returns {boolean} True if the given string is a valid gradient.
*/
function isValidGradient(colors) {
function isValidGradient(colors: string[]) {
return isValidHexColor(colors[1]) && isValidHexColor(colors[2]);
}
@@ -104,7 +114,7 @@ function isValidGradient(colors) {
* @param {string} fallbackColor
* @returns {string | string[]}
*/
function fallbackColor(color, fallbackColor) {
function fallbackColor(color: string, fallbackColor: string) {
let colors = color.split(",");
let gradient = null;
@@ -122,7 +132,10 @@ function fallbackColor(color, fallbackColor) {
* @param {import('axios').AxiosRequestConfig['data']} data
* @param {import('axios').AxiosRequestConfig['headers']} headers
*/
function request(data, headers) {
function request(
data: AxiosRequestConfig["data"],
headers: AxiosRequestHeaders,
) {
// @ts-ignore
return axios({
url: "https://api.github.com/graphql",
@@ -145,16 +158,26 @@ function request(data, headers) {
* Auto layout utility, allows us to layout things
* vertically or horizontally with proper gaping
*/
function flexLayout({ items, gap, direction, sizes = [] }) {
function flexLayout({
items,
gap,
direction,
sizes = [],
}: {
items: string[];
gap: number;
direction?: "column" | "row";
sizes?: number[];
}) {
let lastSize = 0;
// filter() for filtering out empty strings
return items.filter(Boolean).map((item, i) => {
return items.filter(Boolean).map((item: string, i: number) => {
const size = sizes[i] || 0;
let transform = `translate(${lastSize}, 0)`;
if (direction === "column") {
transform = `translate(0, ${lastSize})`;
}
lastSize += size + gap;
lastSize += ((size as number) + gap) as number;
return `<g transform="${transform}">${item}</g>`;
});
}
@@ -174,13 +197,21 @@ function flexLayout({ items, gap, direction, sizes = [] }) {
* @param {CardColors} options
*/
function getCardColors({
title_color,
text_color,
icon_color,
bg_color,
border_color,
title_color = "",
text_color = "",
icon_color = "",
bg_color = "",
border_color = "",
theme,
fallbackTheme = "default",
}: {
title_color?: string;
text_color?: string;
icon_color?: string;
bg_color?: string;
border_color?: string;
theme: ThemeEnum;
fallbackTheme?: "default";
}) {
const defaultTheme = themes[fallbackTheme];
const selectedTheme = themes[theme] || defaultTheme;
@@ -220,7 +251,7 @@ function getCardColors({
* @param {number} maxLines
* @returns {string[]}
*/
function wrapTextMultiline(text, width = 59, maxLines = 3) {
function wrapTextMultiline(text: string, width = 59, maxLines = 3) {
const fullWidthComma = "";
const encoded = encodeHTML(text);
const isChinese = encoded.includes(fullWidthComma);
@@ -266,26 +297,30 @@ const SECONDARY_ERROR_MESSAGES = {
};
class CustomError extends Error {
type: string;
secondaryMessage?: string;
/**
* @param {string} message
* @param {string} type
*/
constructor(message, type) {
constructor(message: string, type: keyof typeof SECONDARY_ERROR_MESSAGES) {
super(message);
this.type = type;
this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
}
static MAX_RETRY = "MAX_RETRY";
static USER_NOT_FOUND = "USER_NOT_FOUND";
static MAX_RETRY = "MAX_RETRY" as "MAX_RETRY";
static USER_NOT_FOUND = "USER_NOT_FOUND" as "USER_NOT_FOUND";
}
class MissingParamError extends Error {
missedParams: string[];
secondaryMessage?: string;
/**
* @param {string[]} missedParams
* @param {string?=} secondaryMessage
*/
constructor(missedParams, secondaryMessage) {
constructor(missedParams: string[], secondaryMessage?: string) {
const msg = `Missing params ${missedParams
.map((p) => `"${p}"`)
.join(", ")} make sure you pass the parameters in URL`;
@@ -301,7 +336,7 @@ class MissingParamError extends Error {
* @param {number} fontSize
* @returns
*/
function measureText(str, fontSize = 10) {
function measureText(str: string, fontSize = 10) {
// prettier-ignore
const widths = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -336,23 +371,23 @@ function measureText(str, fontSize = 10) {
}
/** @param {string} name */
const lowercaseTrim = (name) => name.toLowerCase().trim();
const lowercaseTrim = (name: string) => name.toLowerCase().trim();
/**
* @template T
* @param {Array<T>} arr
* @param {number} perChunk
* @returns {Array<T>}
* @returns {Array<T><T>}
*/
function chunkArray(arr, perChunk) {
return arr.reduce((resultArray, item, index) => {
function chunkArray<T>(arr: Array<T>, perChunk: number) {
return arr.reduce((resultArray: Array<T> | Array<Array<T>>, item, index) => {
const chunkIndex = Math.floor(index / perChunk);
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = []; // start a new chunk
}
resultArray[chunkIndex].push(item);
(resultArray[chunkIndex] as Array<T>).push(item);
return resultArray;
}, []);
@@ -363,7 +398,7 @@ function chunkArray(arr, perChunk) {
* @param {string} str
* @returns {string}
*/
function parseEmojis(str) {
function parseEmojis(str: string) {
if (!str) throw new Error("[parseEmoji]: str argument not provided");
return str.replace(/:\w+:/gm, (emoji) => {
return toEmoji.get(emoji) || "";
@@ -1,12 +1,13 @@
// @ts-check
import { retryer } from "../common/retryer.js";
import { MissingParamError, request } from "../common/utils.js";
import { AxiosRequestHeaders } from "axios";
import { retryer } from "../common/retryer";
import { MissingParamError, request } from "../common/utils";
/**
* @param {import('Axios').AxiosRequestHeaders} variables
* @param {AxiosRequestHeaders} variables
* @param {string} token
*/
const fetcher = (variables, token) => {
const fetcher = (variables: AxiosRequestHeaders, token: string) => {
return request(
{
query: `
@@ -55,7 +56,7 @@ const urlExample = "/api/pin?username=USERNAME&amp;repo=REPO_NAME";
* @param {string} reponame
* @returns {Promise<import("./types").RepositoryData>}
*/
async function fetchRepo(username, reponame) {
async function fetchRepo(username: string, reponame: string) {
if (!username && !reponame) {
throw new MissingParamError(["username", "repo"], urlExample);
}
@@ -2,14 +2,14 @@
import axios from "axios";
import * as dotenv from "dotenv";
import githubUsernameRegex from "github-username-regex";
import { calculateRank } from "../calculateRank.js";
import { retryer } from "../common/retryer.js";
import { calculateRank } from "../calculateRank";
import { retryer } from "../common/retryer";
import {
CustomError,
logger,
MissingParamError,
request,
} from "../common/utils.js";
} from "../common/utils";
dotenv.config();
@@ -29,10 +29,10 @@ const fetcher = (variables, token) => {
totalCommitContributions
restrictedContributionsCount
}
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
repositoriesContributedTo(contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
totalCount
}
pullRequests(first: 1) {
pullRequests {
totalCount
}
openIssues: issues(states: OPEN) {
@@ -44,14 +44,41 @@ const fetcher = (variables, token) => {
followers {
totalCount
}
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
repositories(ownerAffiliations: OWNER) {
totalCount
}
}
}
`,
variables,
},
{
Authorization: `bearer ${token}`,
},
);
};
/**
* @param {import('axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const repositoriesFetcher = (variables, token) => {
return request(
{
query: `
query userInfo($login: String!, $after: String) {
user(login: $login) {
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) {
nodes {
name
stargazers {
totalCount
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
@@ -99,6 +126,43 @@ const totalCommitsFetcher = async (username) => {
return 0;
};
/**
* Fetch all the stars for all the repositories of a given username
* @param {string} username
* @param {array} repoToHide
*/
const totalStarsFetcher = async (username, repoToHide) => {
let nodes = [];
let hasNextPage = true;
let endCursor = null;
while (hasNextPage) {
const variables = { login: username, first: 100, after: endCursor };
let res = await retryer(repositoriesFetcher, variables);
if (res.data.errors) {
logger.error(res.data.errors);
throw new CustomError(
res.data.errors[0].message || "Could not fetch user",
CustomError.USER_NOT_FOUND,
);
}
const allNodes = res.data.data.user.repositories.nodes;
const nodesWithStars = allNodes.filter(
(node) => node.stargazers.totalCount !== 0,
);
nodes.push(...nodesWithStars);
hasNextPage =
allNodes.length === nodesWithStars.length &&
res.data.data.user.repositories.pageInfo.hasNextPage;
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
}
return nodes
.filter((data) => !repoToHide[data.name])
.reduce((prev, curr) => prev + curr.stargazers.totalCount, 0);
};
/**
* @param {string} username
* @param {boolean} count_private
@@ -166,13 +230,7 @@ async function fetchStats(
stats.contributedTo = user.repositoriesContributedTo.totalCount;
// Retrieve stars while filtering out repositories to be hidden
stats.totalStars = user.repositories.nodes
.filter((data) => {
return !repoToHide[data.name];
})
.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
stats.totalStars = await totalStarsFetcher(username, repoToHide);
stats.rank = calculateRank({
totalCommits: stats.totalCommits,
@@ -1,7 +1,7 @@
// @ts-check
import * as dotenv from "dotenv";
import { retryer } from "../common/retryer.js";
import { logger, MissingParamError, request } from "../common/utils.js";
import { retryer } from "../common/retryer";
import { logger, MissingParamError, request } from "../common/utils";
dotenv.config();
@@ -1,5 +1,5 @@
import axios from "axios";
import { MissingParamError } from "../common/utils.js";
import { MissingParamError } from "../common/utils";
/**
* @param {{username: string, api_domain: string, range: string}} props
+1 -1
View File
@@ -1,4 +1,4 @@
import { encodeHTML } from "./common/utils.js";
import { encodeHTML } from "./common/utils";
const statCardLocales = ({ name, apostrophe }) => {
const encodedName = encodeHTML(name);
+6
View File
@@ -0,0 +1,6 @@
/**
* @file Contains global type definitions.
*/
// Declare global emoji-name-map module since it doesn't have a type definition.
declare module "emoji-name-map";
+3
View File
@@ -0,0 +1,3 @@
/**
* @file Contains shared types.
*/
@@ -1,161 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Test Render Wakatime Card should render correctly 1`] = `[Function]`;
exports[`Test Render Wakatime Card should render correctly with compact layout 1`] = `
"
<svg
width="495"
height="115"
viewBox="0 0 495 115"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="descId"
>
<title id="titleId"></title>
<desc id="descId"></desc>
<style>
.header {
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
fill: #2f80ed;
animation: fadeInAnimation 0.8s ease-in-out forwards;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.header { font-size: 15.5px; }
}
.stat {
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: #434d58;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.stat { font-size:12px; }
}
.stagger {
opacity: 0;
animation: fadeInAnimation 0.3s ease-in-out forwards;
}
.rank-text {
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58;
animation: scaleInAnimation 0.3s ease-in-out forwards;
}
.not_bold { font-weight: 400 }
.bold { font-weight: 700 }
.icon {
fill: #4c71f2;
display: none;
}
.rank-circle-rim {
stroke: #2f80ed;
fill: none;
stroke-width: 6;
opacity: 0.2;
}
.rank-circle {
stroke: #2f80ed;
stroke-dasharray: 250;
fill: none;
stroke-width: 6;
stroke-linecap: round;
opacity: 0.8;
transform-origin: -10px 8px;
transform: rotate(-90deg);
animation: rankAnimation 1s forwards ease-in-out;
}
.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 }
</style>
<rect
data-testid="card-bg"
x="0.5"
y="0.5"
rx="4.5"
height="99%"
stroke="#e4e2e2"
width="494"
fill="#fffefe"
stroke-opacity="1"
/>
<g
data-testid="card-title"
transform="translate(25, 35)"
>
<g transform="translate(0, 0)">
<text
x="0"
y="0"
class="header"
data-testid="header"
>Wakatime Stats</text>
</g>
</g>
<g
data-testid="main-card-body"
transform="translate(0, 55)"
>
<svg x="0" y="0" width="100%">
<mask id="rect-mask">
<rect x="25" y="0" width="440" height="8" fill="white" rx="5" />
</mask>
<rect
mask="url(#rect-mask)"
data-testid="lang-progress"
x="0"
y="0"
width="6.6495"
height="8"
fill="#858585"
/>
<rect
mask="url(#rect-mask)"
data-testid="lang-progress"
x="6.6495"
y="0"
width="0.465"
height="8"
fill="#3178c6"
/>
<g transform="translate(25, 25)">
<circle cx="5" cy="6" r="5" fill="#858585" />
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
Other - 19 mins
</text>
</g>
<g transform="translate(230, 25)">
<circle cx="5" cy="6" r="5" fill="#3178c6" />
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
TypeScript - 1 min
</text>
</g>
</svg>
</g>
</svg>
"
`;
+22 -6
View File
@@ -1,10 +1,10 @@
import { jest } from "@jest/globals";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import api from "../api/index.js";
import { calculateRank } from "../src/calculateRank.js";
import { renderStatsCard } from "../src/cards/stats-card.js";
import { CONSTANTS, renderError } from "../src/common/utils.js";
import api from "../api/index";
import { calculateRank } from "../src/calculateRank";
import { renderStatsCard } from "../src/cards/stats-card";
import { CONSTANTS, renderError } from "../src/common/utils";
const stats = {
name: "Anurag Hazra",
@@ -40,7 +40,20 @@ const data = {
followers: { totalCount: 0 },
repositories: {
totalCount: 1,
},
},
},
};
const repositoriesData = {
data: {
user: {
repositories: {
nodes: [{ stargazers: { totalCount: 100 } }],
pageInfo: {
hasNextPage: false,
cursor: "cursor",
},
},
},
},
@@ -70,7 +83,11 @@ const faker = (query, data) => {
setHeader: jest.fn(),
send: jest.fn(),
};
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, repositoriesData);
return { req, res };
};
@@ -138,7 +155,6 @@ describe("Test /api/", () => {
it("should have proper cache", async () => {
const { req, res } = faker({}, data);
mock.onPost("https://api.github.com/graphql").reply(200, data);
await api(req, res);
@@ -1,5 +1,5 @@
import "@testing-library/jest-dom";
import { calculateRank } from "../src/calculateRank.js";
import { calculateRank } from "../src/calculateRank";
describe("Test calculateRank", () => {
it("should calculate rank correctly", () => {
+3 -3
View File
@@ -1,9 +1,9 @@
import { queryByTestId } from "@testing-library/dom";
import "@testing-library/jest-dom";
import { cssToObject } from "@uppercod/css-to-object";
import { Card } from "../src/common/Card.js";
import { icons } from "../src/common/icons.js";
import { getCardColors } from "../src/common/utils.js";
import { Card } from "../src/common/Card";
import { icons } from "../src/common/icons";
import { getCardColors } from "../src/common/utils";
describe("Card", () => {
it("should hide border", () => {
@@ -6,10 +6,10 @@ dotenv.config();
import { describe } from "@jest/globals";
import axios from "axios";
import { renderRepoCard } from "../../src/cards/repo-card.js";
import { renderStatsCard } from "../../src/cards/stats-card.js";
import { renderTopLanguages } from "../../src/cards/top-languages-card.js";
import { renderWakatimeCard } from "../../src/cards/wakatime-card.js";
import { renderRepoCard } from "../../src/cards/repo-card";
import { renderStatsCard } from "../../src/cards/stats-card";
import { renderTopLanguages } from "../../src/cards/top-languages-card";
import { renderWakatimeCard } from "../../src/cards/wakatime-card";
// Script variables
const REPO = "dummy-cra";
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { fetchRepo } from "../src/fetchers/repo-fetcher.js";
import { fetchRepo } from "../src/fetchers/repo-fetcher";
const data_repo = {
repository: {
@@ -1,8 +1,8 @@
import "@testing-library/jest-dom";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { calculateRank } from "../src/calculateRank.js";
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
import { calculateRank } from "../src/calculateRank";
import { fetchStats } from "../src/fetchers/stats-fetcher";
const data = {
data: {
@@ -19,13 +19,61 @@ const data = {
followers: { totalCount: 100 },
repositories: {
totalCount: 5,
},
},
},
};
const firstRepositoriesData = {
data: {
user: {
repositories: {
nodes: [
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
],
pageInfo: {
hasNextPage: true,
cursor: "cursor",
},
},
},
},
};
const secondRepositoriesData = {
data: {
user: {
repositories: {
nodes: [
{ name: "test-repo-4", stargazers: { totalCount: 50 } },
{ name: "test-repo-5", stargazers: { totalCount: 50 } },
],
pageInfo: {
hasNextPage: false,
cursor: "cursor",
},
},
},
},
};
const repositoriesWithZeroStarsData = {
data: {
user: {
repositories: {
nodes: [
{ name: "test-repo-1", stargazers: { totalCount: 100 } },
{ name: "test-repo-2", stargazers: { totalCount: 100 } },
{ name: "test-repo-3", stargazers: { totalCount: 100 } },
{ name: "test-repo-4", stargazers: { totalCount: 0 } },
{ name: "test-repo-5", stargazers: { totalCount: 0 } },
],
pageInfo: {
hasNextPage: true,
cursor: "cursor",
},
},
},
},
@@ -44,14 +92,22 @@ const error = {
const mock = new MockAdapter(axios);
beforeEach(() => {
mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, firstRepositoriesData)
.onPost("https://api.github.com/graphql")
.replyOnce(200, secondRepositoriesData);
});
afterEach(() => {
mock.reset();
});
describe("Test fetchStats", () => {
it("should fetch correct stats", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
@@ -74,7 +130,38 @@ describe("Test fetchStats", () => {
});
});
it("should stop fetching when there are repos with zero stars", async () => {
mock.reset();
mock
.onPost("https://api.github.com/graphql")
.replyOnce(200, data)
.onPost("https://api.github.com/graphql")
.replyOnce(200, repositoriesWithZeroStarsData);
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
totalCommits: 100,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 100,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank,
});
});
it("should throw error", async () => {
mock.reset();
mock.onPost("https://api.github.com/graphql").reply(200, error);
await expect(fetchStats("anuraghazra")).rejects.toThrow(
@@ -83,8 +170,6 @@ describe("Test fetchStats", () => {
});
it("should fetch and add private contributions", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
let stats = await fetchStats("anuraghazra", true);
const rank = calculateRank({
totalCommits: 150,
@@ -108,7 +193,6 @@ describe("Test fetchStats", () => {
});
it("should fetch total commits", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });
@@ -136,7 +220,6 @@ describe("Test fetchStats", () => {
});
it("should exclude stars of the `test-repo-1` repository", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data);
mock
.onGet("https://api.github.com/search/commits?q=author:anuraghazra")
.reply(200, { total_count: 1000 });
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher";
const mock = new MockAdapter(axios);
@@ -1,7 +1,7 @@
import "@testing-library/jest-dom";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher";
const mock = new MockAdapter(axios);
afterEach(() => {
@@ -1,4 +1,4 @@
import { flexLayout } from "../src/common/utils.js";
import { flexLayout } from "../src/common/utils";
describe("flexLayout", () => {
it("should work with row & col layouts", () => {
+3 -3
View File
@@ -2,9 +2,9 @@ import { jest } from "@jest/globals";
import "@testing-library/jest-dom";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import pin from "../api/pin.js";
import { renderRepoCard } from "../src/cards/repo-card.js";
import { renderError } from "../src/common/utils.js";
import pin from "../api/pin";
import { renderRepoCard } from "../src/cards/repo-card";
import { renderError } from "../src/common/utils";
const data_repo = {
repository: {
@@ -1,9 +1,9 @@
import { queryByTestId } from "@testing-library/dom";
import "@testing-library/jest-dom";
import { cssToObject } from "@uppercod/css-to-object";
import { renderRepoCard } from "../src/cards/repo-card.js";
import { renderRepoCard } from "../src/cards/repo-card";
import { themes } from "../themes/index.js";
import { themes } from "../themes/index";
const data_repo = {
repository: {
@@ -4,11 +4,11 @@ import {
queryByTestId,
} from "@testing-library/dom";
import { cssToObject } from "@uppercod/css-to-object";
import { renderStatsCard } from "../src/cards/stats-card.js";
import { renderStatsCard } from "../src/cards/stats-card";
// adds special assertions like toHaveTextContent
import "@testing-library/jest-dom";
import { themes } from "../themes/index.js";
import { themes } from "../themes/index";
describe("Test renderStatsCard", () => {
const stats = {
@@ -3,11 +3,11 @@ import { cssToObject } from "@uppercod/css-to-object";
import {
MIN_CARD_WIDTH,
renderTopLanguages,
} from "../src/cards/top-languages-card.js";
} from "../src/cards/top-languages-card";
// adds special assertions like toHaveTextContent
import "@testing-library/jest-dom";
import { themes } from "../themes/index.js";
import { themes } from "../themes/index";
describe("Test renderTopLanguages", () => {
const langs = {
@@ -1,8 +1,8 @@
import { queryByTestId } from "@testing-library/dom";
import "@testing-library/jest-dom";
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
import { getCardColors } from "../src/common/utils.js";
import { wakaTimeData } from "./fetchWakatime.test.js";
import { renderWakatimeCard } from "../src/cards/wakatime-card";
import { getCardColors } from "../src/common/utils";
import { wakaTimeData } from "./fetchWakatime.test";
describe("Test Render Wakatime Card", () => {
it("should render correctly", () => {
@@ -1,7 +1,7 @@
import { jest } from "@jest/globals";
import "@testing-library/jest-dom";
import { retryer } from "../src/common/retryer.js";
import { logger } from "../src/common/utils.js";
import { retryer } from "../src/common/retryer";
import { logger } from "../src/common/utils";
const fetcher = jest.fn((variables, token) => {
logger.log(variables, token);
@@ -2,9 +2,9 @@ import { jest } from "@jest/globals";
import "@testing-library/jest-dom";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import topLangs from "../api/top-langs.js";
import { renderTopLanguages } from "../src/cards/top-languages-card.js";
import { renderError } from "../src/common/utils.js";
import topLangs from "../api/top-langs";
import { renderTopLanguages } from "../src/cards/top-languages-card";
import { renderError } from "../src/common/utils";
const data_langs = {
data: {
+2 -2
View File
@@ -6,9 +6,9 @@ import {
kFormatter,
renderError,
wrapTextMultiline,
} from "../src/common/utils.js";
} from "../src/common/utils";
describe("Test utils.js", () => {
describe("Test utils", () => {
it("should test kFormatter", () => {
expect(kFormatter(1)).toBe(1);
expect(kFormatter(-1)).toBe(-1);
+84 -2
View File
@@ -1,4 +1,27 @@
export const themes = {
/** Theme properties. */
interface ThemeProperties {
/** Title color. */
title_color: string;
/** Icon color. */
icon_color: string;
/** Text color. */
text_color: string;
/** Background color. */
bg_color: string;
/** Border color. */
border_color: string;
}
/** Card theme. */
interface Theme {
[index: string]: ThemeProperties;
}
/**
* Themes for the cards.
*/
export const themes: Theme = {
default: {
title_color: "2f80ed",
icon_color: "4c71f2",
@@ -11,348 +34,406 @@ export const themes = {
icon_color: "586069", // icon color is different
text_color: "434d58",
bg_color: "fffefe",
border_color: "",
},
transparent: {
title_color: "006AFF",
icon_color: "0579C3",
text_color: "417E87",
bg_color: "ffffff00",
border_color: "",
},
dark: {
title_color: "fff",
icon_color: "79ff97",
text_color: "9f9f9f",
bg_color: "151515",
border_color: "",
},
radical: {
title_color: "fe428e",
icon_color: "f8d847",
text_color: "a9fef7",
bg_color: "141321",
border_color: "",
},
merko: {
title_color: "abd200",
icon_color: "b7d364",
text_color: "68b587",
bg_color: "0a0f0b",
border_color: "",
},
gruvbox: {
title_color: "fabd2f",
icon_color: "fe8019",
text_color: "8ec07c",
bg_color: "282828",
border_color: "",
},
gruvbox_light: {
title_color: "b57614",
icon_color: "af3a03",
text_color: "427b58",
bg_color: "fbf1c7",
border_color: "",
},
tokyonight: {
title_color: "70a5fd",
icon_color: "bf91f3",
text_color: "38bdae",
bg_color: "1a1b27",
border_color: "",
},
onedark: {
title_color: "e4bf7a",
icon_color: "8eb573",
text_color: "df6d74",
bg_color: "282c34",
border_color: "",
},
cobalt: {
title_color: "e683d9",
icon_color: "0480ef",
text_color: "75eeb2",
bg_color: "193549",
border_color: "",
},
synthwave: {
title_color: "e2e9ec",
icon_color: "ef8539",
text_color: "e5289e",
bg_color: "2b213a",
border_color: "",
},
highcontrast: {
title_color: "e7f216",
icon_color: "00ffff",
text_color: "fff",
bg_color: "000",
border_color: "",
},
dracula: {
title_color: "ff6e96",
icon_color: "79dafa",
text_color: "f8f8f2",
bg_color: "282a36",
border_color: "",
},
prussian: {
title_color: "bddfff",
icon_color: "38a0ff",
text_color: "6e93b5",
bg_color: "172f45",
border_color: "",
},
monokai: {
title_color: "eb1f6a",
icon_color: "e28905",
text_color: "f1f1eb",
bg_color: "272822",
border_color: "",
},
vue: {
title_color: "41b883",
icon_color: "41b883",
text_color: "273849",
bg_color: "fffefe",
border_color: "",
},
"vue-dark": {
title_color: "41b883",
icon_color: "41b883",
text_color: "fffefe",
bg_color: "273849",
border_color: "",
},
"shades-of-purple": {
title_color: "fad000",
icon_color: "b362ff",
text_color: "a599e9",
bg_color: "2d2b55",
border_color: "",
},
nightowl: {
title_color: "c792ea",
icon_color: "ffeb95",
text_color: "7fdbca",
bg_color: "011627",
border_color: "",
},
buefy: {
title_color: "7957d5",
icon_color: "ff3860",
text_color: "363636",
bg_color: "ffffff",
border_color: "",
},
"blue-green": {
title_color: "2f97c1",
icon_color: "f5b700",
text_color: "0cf574",
bg_color: "040f0f",
border_color: "",
},
algolia: {
title_color: "00AEFF",
icon_color: "2DDE98",
text_color: "FFFFFF",
bg_color: "050F2C",
border_color: "",
},
"great-gatsby": {
title_color: "ffa726",
icon_color: "ffb74d",
text_color: "ffd95b",
bg_color: "000000",
border_color: "",
},
darcula: {
title_color: "BA5F17",
icon_color: "84628F",
text_color: "BEBEBE",
bg_color: "242424",
border_color: "",
},
bear: {
title_color: "e03c8a",
icon_color: "00AEFF",
text_color: "bcb28d",
bg_color: "1f2023",
border_color: "",
},
"solarized-dark": {
title_color: "268bd2",
icon_color: "b58900",
text_color: "859900",
bg_color: "002b36",
border_color: "",
},
"solarized-light": {
title_color: "268bd2",
icon_color: "b58900",
text_color: "859900",
bg_color: "fdf6e3",
border_color: "",
},
"chartreuse-dark": {
title_color: "7fff00",
icon_color: "00AEFF",
text_color: "fff",
bg_color: "000",
border_color: "",
},
nord: {
title_color: "81a1c1",
text_color: "d8dee9",
icon_color: "88c0d0",
bg_color: "2e3440",
border_color: "",
},
gotham: {
title_color: "2aa889",
icon_color: "599cab",
text_color: "99d1ce",
bg_color: "0c1014",
border_color: "",
},
"material-palenight": {
title_color: "c792ea",
icon_color: "89ddff",
text_color: "a6accd",
bg_color: "292d3e",
border_color: "",
},
graywhite: {
title_color: "24292e",
icon_color: "24292e",
text_color: "24292e",
bg_color: "ffffff",
border_color: "",
},
"vision-friendly-dark": {
title_color: "ffb000",
icon_color: "785ef0",
text_color: "ffffff",
bg_color: "000000",
border_color: "",
},
"ayu-mirage": {
title_color: "f4cd7c",
icon_color: "73d0ff",
text_color: "c7c8c2",
bg_color: "1f2430",
border_color: "",
},
"midnight-purple": {
title_color: "9745f5",
icon_color: "9f4bff",
text_color: "ffffff",
bg_color: "000000",
border_color: "",
},
calm: {
title_color: "e07a5f",
icon_color: "edae49",
text_color: "ebcfb2",
bg_color: "373f51",
border_color: "",
},
"flag-india": {
title_color: "ff8f1c",
icon_color: "250E62",
text_color: "509E2F",
bg_color: "ffffff",
border_color: "",
},
omni: {
title_color: "FF79C6",
icon_color: "e7de79",
text_color: "E1E1E6",
bg_color: "191622",
border_color: "",
},
react: {
title_color: "61dafb",
icon_color: "61dafb",
text_color: "ffffff",
bg_color: "20232a",
border_color: "",
},
jolly: {
title_color: "ff64da",
icon_color: "a960ff",
text_color: "ffffff",
bg_color: "291B3E",
border_color: "",
},
maroongold: {
title_color: "F7EF8A",
icon_color: "F7EF8A",
text_color: "E0AA3E",
bg_color: "260000",
border_color: "",
},
yeblu: {
title_color: "ffff00",
icon_color: "ffff00",
text_color: "ffffff",
bg_color: "002046",
border_color: "",
},
blueberry: {
title_color: "82aaff",
icon_color: "89ddff",
text_color: "27e8a7",
bg_color: "242938",
border_color: "",
},
slateorange: {
title_color: "faa627",
icon_color: "faa627",
text_color: "ffffff",
bg_color: "36393f",
border_color: "",
},
kacho_ga: {
title_color: "bf4a3f",
icon_color: "a64833",
text_color: "d9c8a9",
bg_color: "402b23",
border_color: "",
},
outrun: {
title_color: "ffcc00",
icon_color: "ff1aff",
text_color: "8080ff",
bg_color: "141439",
border_color: "",
},
ocean_dark: {
title_color: "8957B2",
icon_color: "FFFFFF",
text_color: "92D534",
bg_color: "151A28",
border_color: "",
},
city_lights: {
title_color: "5D8CB3",
icon_color: "4798FF",
text_color: "718CA1",
bg_color: "1D252C",
border_color: "",
},
github_dark: {
title_color: "58A6FF",
icon_color: "1F6FEB",
text_color: "C3D1D9",
bg_color: "0D1117",
border_color: "",
},
discord_old_blurple: {
title_color: "7289DA",
icon_color: "7289DA",
text_color: "FFFFFF",
bg_color: "2C2F33",
border_color: "",
},
aura_dark: {
title_color: "ff7372",
icon_color: "6cffd0",
text_color: "dbdbdb",
bg_color: "252334",
border_color: "",
},
panda: {
title_color: "19f9d899",
icon_color: "19f9d899",
text_color: "FF75B5",
bg_color: "31353a",
border_color: "",
},
noctis_minimus: {
title_color: "d3b692",
icon_color: "72b7c0",
text_color: "c5cdd3",
bg_color: "1b2932",
border_color: "",
},
cobalt2: {
title_color: "ffc600",
icon_color: "ffffff",
text_color: "0088ff",
bg_color: "193549",
border_color: "",
},
swift: {
title_color: "000000",
icon_color: "f05237",
text_color: "000000",
bg_color: "f7f7f7",
border_color: "",
},
aura: {
title_color: "a277ff",
icon_color: "ffca85",
text_color: "61ffca",
bg_color: "15141b",
border_color: "",
},
apprentice: {
title_color: "ffffff",
icon_color: "ffffaf",
text_color: "bcbcbc",
bg_color: "262626",
border_color: "",
},
moltack: {
title_color: "86092C",
icon_color: "86092C",
text_color: "574038",
bg_color: "F5E1C0",
border_color: "",
},
codeSTACKr: {
title_color: "ff652f",
@@ -366,7 +447,8 @@ export const themes = {
icon_color: "ebbcba",
text_color: "e0def4",
bg_color: "191724",
border_color: "",
},
};
export default themes;
export type ThemeEnum = keyof typeof themes;
+103
View File
@@ -0,0 +1,103 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": ["./node_modules/@types", "./src/types"],
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"functions": {
"api/*.js": {
"api/*.ts": {
"memory": 128,
"maxDuration": 30
}