Compare commits

...
Author SHA1 Message Date
rickstaa 6883fec5c7 Merge branch 'master' into theme_preview_batch 2022-10-02 17:43:51 +02:00
rickstaa 90f1ed40f9 ci: add batch theme preview script 2022-10-02 17:43:48 +02:00
rickstaa 5d42af59c0 ci: fix small bug in theme-preview action 2022-10-02 17:42:58 +02:00
Rick Staa 822efbf6cb ci: fix small bugs in theme-preview action (#2093)
* ci: fix small bugs in theme-preview action

* ci: format code
2022-10-02 17:41:28 +02:00
Anurag HazraandGithub Readme Stats Bot 84c9d354b3 docs(theme): Auto update theme readme (#2091)
Co-authored-by: Github Readme Stats Bot <no-reply@githubreadmestats.com>
2022-10-02 17:31:38 +02:00
Rick Staa d56b294ca1 style: improve code style 2022-10-02 14:50:03 +02:00
rickstaa 6f8bd9fe10 ci: increase top-issues dashboard update frequency 2022-10-02 14:15:25 +02:00
Jagruti TiwariandRick Staa f0daab11c8 feat: added default options to readme (#2051)
* feat: added default options to readme

* Update readme.md

Co-authored-by: Rick Staa <rick.staa@outlook.com>

* fix: added default values for stats card

* style: some small readme changes

Co-authored-by: Rick Staa <rick.staa@outlook.com>
2022-10-02 14:03:22 +02:00
rickstaa 3a07cb02db ci: fix e2e test 2022-10-02 12:27:34 +02:00
rickstaa 510cf6266c feat: remove border color from transparent theme 2022-10-02 11:47:56 +02:00
Rick Staa 406dbc5656 feat: add transparent theme
Adds a transparent theme with a contrast ratio of 4.5 on both black and white (see https://ux.stackexchange.com/a/86226).
2022-10-02 11:45:55 +02:00
Rick Staa b8faef6857 ci: add stale theme pull request closer action push (#2067)
This commit adds the `stale-theme-pr-closer` closer action. This action
is used to close theme pull requests with an `invalid` label that has
been stale for a given number of `STALE_DAYS`.
2022-10-02 11:05:13 +02:00
rickstaa 986070a597 Merge branch 'master' of github.com:anuraghazra/github-readme-stats 2022-10-02 10:52:39 +02:00
rickstaa 51e7887b95 ci: fix e2e test 2022-10-02 10:52:33 +02:00
Oscar Dominguezandrickstaa 098331069a ci(label-pr): upgrade actions/labeler to v4 (#2080)
Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-10-02 10:51:37 +02:00
Jongwoo Han f86059f097 chore: Update workflows to cache dependencies (#2083) 2022-10-02 10:38:07 +02:00
rickstaa aaf710c024 ci: fix e2e test 2022-10-02 10:35:11 +02:00
Rick Staa d5d8912954 ci: improve theme preview action (#2088)
This commit makes sure that 4,5,6,8 digit hex colors are also accepted.
It further also includes the border_color attribute in the tests.
2022-10-02 10:33:29 +02:00
Rick Staa 6b7e91abb6 ci: improve theme-preview actiot push (#2065)
This commit makes sure that:
 - The theme-preview action can test multiple themes.
 - Fails if something goes wrong.
 - Requests changes if the theme does not adhere to the guidelines.
 - Approves the PR if the theme adheres to the guidelines.
2022-10-01 14:01:29 +02:00
Rick Staa f8e32f9e0c ci: add e2e vercel test action (#2054)
* ci: add e2e vercel test action

This commit adds several end-to-end tests that can be used
to test whether the Vercel Preview deployment successfully
returns the cards.

* test: add additional e2e card tests

* test: improve e2e card tests

This commit makes sure that the tests also check whether a valid cards
are returned from the preview deployment.
2022-10-01 13:59:02 +02:00
rickstaa fe1ca87e33 ci: filter #1772 from top-issues-dashboard 2022-09-25 11:16:37 +02:00
Rick Staa 9a909ff0a4 build: fix wakatime json import bug (#2050)
* build: fix vercel node version

* build: remove engine property

* fix: add json import workarround

This commit adds a workaround for importing the `languageColors` JSON
file. This needed to be done since Vercel uses v16.4, which does not
support JSON file importing without the `experimental-json-modules`
flag. See https://simonplend.com/import-json-in-es-modules/
for more information. The workaround can be found [here](https://stackoverflow.com/questions/66726365/how-should-i-import-json-in-node).
2022-09-24 15:37:19 +02:00
rickstaa dbf6560781 build: update package-lock file 2022-09-24 14:17:32 +02:00
rickstaa 8886b93a7e refactor: clean up imports 2022-09-24 14:10:06 +02:00
rickstaa 41d6760abd fix: fix es6 import file extension 2022-09-24 13:20:52 +02:00
Rick Staa c7d4c29757 ci: switch to npm ci in github actions (#2046) 2022-09-24 10:37:24 +02:00
rickstaa 0de23d3762 fix: fix generate-theme-doc bug 2022-09-24 10:33:19 +02:00
rsk2andrickstaa 107f7ca52c Feature/grs 1955 change commonjs imports (#1995)
* GRS-1955: Using ES6 import/export in src files

* GRS-1955: Using ES6 import/export in test files

* GRS-1955: Using ES6 import/export in themes index.js

* GRS-1955: Readding blank line at end of top-languages-card.js

* feat: fix test es6 import errors

This commit makes sure jest is set-up to support es6. It also fixes
several test errors and sorts the imports.

* test: update test node version

This commit makes sure node 16 is used in the github actions.

* refactor: run prettier

Co-authored-by: rickstaa <rick.staa@outlook.com>
2022-09-24 10:20:54 +02:00
Rick Staa 40735124ed ci: change theme-preview action run condition (#1903)
This commit makes sure that the theme-preview action only runs when a
theme was added or changed.
2022-09-17 13:42:16 +02:00
Rick Staa f9b9015ec2 feat(stats): add 'not_bold' option to stats card (#1468) 2022-09-17 13:32:40 +02:00
rickstaa eacc3cd29c fix: add missing StatsCard types 2022-09-17 11:28:50 +02:00
Rick Staa c1324b31bf feat: add 'exclude_repo' option to stats card (#1450)
This commit allows users to exclude repositories for the stats card
using the `exclude_repo` option.
2022-09-16 13:09:19 +02:00
65 changed files with 10969 additions and 690 deletions
+33
View File
@@ -0,0 +1,33 @@
name: Test Deployment
on:
deployment_status:
jobs:
e2eTests:
if:
github.event_name == 'deployment_status' &&
github.event.deployment_status.state == 'success'
name: Perform 2e2 tests
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
env:
CI: true
- name: Run end-to-end tests.
run: npm run test:e2e
env:
VERCEL_PREVIEW_URL: ${{ github.event.deployment_status.target_url }}
@@ -12,6 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 # NOTE: Retrieve issue templates. - uses: actions/checkout@v3 # NOTE: Retrieve issue templates.
- name: Run empty issues closer action - name: Run empty issues closer action
uses: rickstaa/empty-issues-closer-action@v1 uses: rickstaa/empty-issues-closer-action@v1
env: env:
+12 -7
View File
@@ -1,5 +1,4 @@
name: Generate Theme Readme name: Generate Theme Readme
on: on:
push: push:
branches: branches:
@@ -8,19 +7,25 @@ on:
- "themes/index.js" - "themes/index.js"
jobs: jobs:
build: generateThemeDoc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Generate theme doc
strategy:
matrix:
node-version: [16.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: setup node
uses: actions/setup-node@v1 - name: Setup Node
uses: actions/setup-node@v3
with: with:
node-version: "12.x" node-version: ${{ matrix.node-version }}
cache: npm
- name: npm install, generate readme - name: npm install, generate readme
run: | run: |
npm install npm ci
npm run theme-readme-gen npm run theme-readme-gen
env: env:
CI: true CI: true
+1 -1
View File
@@ -6,6 +6,6 @@ jobs:
triage: triage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/labeler@v2 - uses: actions/labeler@v4
with: with:
repo-token: "${{ secrets.GITHUB_TOKEN }}" repo-token: "${{ secrets.GITHUB_TOKEN }}"
+17 -8
View File
@@ -1,24 +1,33 @@
name: Theme preview name: Theme preview
on: on:
pull_request_target: pull_request_target:
types: [opened, synchronize, reopened] types: [opened, edited, reopened, synchronize]
branches: branches:
- master - master
- theme-preview-script paths:
- "themes/index.js" - "themes/index.js"
jobs: jobs:
build: previewTheme:
runs-on: ubuntu-latest
name: Install & Preview name: Install & Preview
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
- uses: bahmutov/npm-install@v1 - uses: bahmutov/npm-install@v1
with: with:
useLockFile: false useLockFile: false
- run: npm run preview-theme - run: npm run preview-theme
env: env:
CI: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_TOKEN: ${{ secrets.PERSONAL_TOKEN }}
@@ -0,0 +1,30 @@
name: Close stale theme pull requests that have the 'invalid' label.
on:
schedule:
- cron: "0 0 */7 * *"
jobs:
closeOldThemePrs:
name: Close stale 'invalid' theme PRs
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
- uses: bahmutov/npm-install@v1
with:
useLockFile: false
- run: npm run close-stale-theme-prs
env:
STALE_DAYS: 15
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+9 -5
View File
@@ -1,5 +1,4 @@
name: Test name: Test
on: on:
push: push:
branches: branches:
@@ -10,19 +9,24 @@ on:
jobs: jobs:
build: build:
name: Perform tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v1 uses: actions/setup-node@v3
with: with:
node-version: "12.x" node-version: ${{ matrix.node-version }}
cache: npm
- name: Install & Test - name: Install & Test
run: | run: |
npm install npm ci
npm run test npm run test
- name: Run Prettier - name: Run Prettier
+2 -1
View File
@@ -1,7 +1,7 @@
name: Update top issues dashboard name: Update top issues dashboard
on: on:
schedule: schedule:
- cron: "0 0 */7 * *" - cron: "0 0 */3 * *"
jobs: jobs:
showAndLabelTopIssues: showAndLabelTopIssues:
@@ -13,6 +13,7 @@ jobs:
env: env:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
with: with:
filter: "1772"
label: false label: false
dashboard: true dashboard: true
dashboard_show_total_reactions: true dashboard_show_total_reactions: true
-1
View File
@@ -1,7 +1,6 @@
.vercel .vercel
.env .env
node_modules node_modules
package-lock.json
*.lock *.lock
.vscode/ .vscode/
.idea/ .idea/
+17 -11
View File
@@ -1,17 +1,19 @@
require("dotenv").config(); import * as dotenv from "dotenv";
const { import { renderStatsCard } from "../src/cards/stats-card.js";
renderError, import { blacklist } from "../src/common/blacklist.js";
parseBoolean, import {
parseArray,
clampValue, clampValue,
CONSTANTS, CONSTANTS,
} = require("../src/common/utils"); parseArray,
const fetchStats = require("../src/fetchers/stats-fetcher"); parseBoolean,
const renderStatsCard = require("../src/cards/stats-card"); renderError,
const blacklist = require("../src/common/blacklist"); } from "../src/common/utils.js";
const { isLocaleAvailable } = require("../src/translations"); import { fetchStats } from "../src/fetchers/stats-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
module.exports = async (req, res) => { dotenv.config();
export default async (req, res) => {
const { const {
username, username,
hide, hide,
@@ -26,9 +28,11 @@ module.exports = async (req, res) => {
title_color, title_color,
icon_color, icon_color,
text_color, text_color,
text_bold,
bg_color, bg_color,
theme, theme,
cache_seconds, cache_seconds,
exclude_repo,
custom_title, custom_title,
locale, locale,
disable_animations, disable_animations,
@@ -50,6 +54,7 @@ module.exports = async (req, res) => {
username, username,
parseBoolean(count_private), parseBoolean(count_private),
parseBoolean(include_all_commits), parseBoolean(include_all_commits),
parseArray(exclude_repo),
); );
const cacheSeconds = clampValue( const cacheSeconds = clampValue(
@@ -73,6 +78,7 @@ module.exports = async (req, res) => {
title_color, title_color,
icon_color, icon_color,
text_color, text_color,
text_bold: parseBoolean(text_bold),
bg_color, bg_color,
theme, theme,
custom_title, custom_title,
+9 -10
View File
@@ -1,16 +1,15 @@
require("dotenv").config(); import { renderRepoCard } from "../src/cards/repo-card.js";
const { import { blacklist } from "../src/common/blacklist.js";
renderError, import {
parseBoolean,
clampValue, clampValue,
CONSTANTS, CONSTANTS,
} = require("../src/common/utils"); parseBoolean,
const fetchRepo = require("../src/fetchers/repo-fetcher"); renderError,
const renderRepoCard = require("../src/cards/repo-card"); } from "../src/common/utils.js";
const blacklist = require("../src/common/blacklist"); import { fetchRepo } from "../src/fetchers/repo-fetcher.js";
const { isLocaleAvailable } = require("../src/translations"); import { isLocaleAvailable } from "../src/translations.js";
module.exports = async (req, res) => { export default async (req, res) => {
const { const {
username, username,
repo, repo,
+13 -11
View File
@@ -1,17 +1,19 @@
require("dotenv").config(); import * as dotenv from "dotenv";
const { import { renderTopLanguages } from "../src/cards/top-languages-card.js";
renderError, import { blacklist } from "../src/common/blacklist.js";
import {
clampValue, clampValue,
parseBoolean,
parseArray,
CONSTANTS, CONSTANTS,
} = require("../src/common/utils"); parseArray,
const fetchTopLanguages = require("../src/fetchers/top-languages-fetcher"); parseBoolean,
const renderTopLanguages = require("../src/cards/top-languages-card"); renderError,
const blacklist = require("../src/common/blacklist"); } from "../src/common/utils.js";
const { isLocaleAvailable } = require("../src/translations"); import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
module.exports = async (req, res) => { dotenv.config();
export default async (req, res) => {
const { const {
username, username,
hide, hide,
+13 -11
View File
@@ -1,16 +1,18 @@
require("dotenv").config(); import * as dotenv from "dotenv";
const { import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
renderError, import {
parseBoolean,
clampValue, clampValue,
parseArray,
CONSTANTS, CONSTANTS,
} = require("../src/common/utils"); parseArray,
const { isLocaleAvailable } = require("../src/translations"); parseBoolean,
const { fetchWakatimeStats } = require("../src/fetchers/wakatime-fetcher"); renderError,
const wakatimeCard = require("../src/cards/wakatime-card"); } from "../src/common/utils.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
import { isLocaleAvailable } from "../src/translations.js";
module.exports = async (req, res) => { dotenv.config();
export default async (req, res) => {
const { const {
username, username,
title_color, title_color,
@@ -56,7 +58,7 @@ module.exports = async (req, res) => {
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`); res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
return res.send( return res.send(
wakatimeCard(stats, { renderWakatimeCard(stats, {
custom_title, custom_title,
hide_title: parseBoolean(hide_title), hide_title: parseBoolean(hide_title),
hide_border: parseBoolean(hide_border), hide_border: parseBoolean(hide_border),
+10 -1
View File
@@ -1,3 +1,12 @@
module.exports = { export default {
clearMocks: true, clearMocks: true,
transform: {},
testEnvironment: "jsdom",
coverageProvider: "v8",
testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/tests/e2e/"],
modulePathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/tests/e2e/"],
coveragePathIgnorePatterns: [
"<rootDir>/node_modules/",
"<rootDir>/tests/E2E/",
],
}; };
+7
View File
@@ -0,0 +1,7 @@
export default {
clearMocks: true,
transform: {},
testEnvironment: "node",
coverageProvider: "v8",
testMatch: ["<rootDir>/tests/e2e/**/*.test.js"],
};
+9398
View File
File diff suppressed because it is too large Load Diff
+12 -9
View File
@@ -3,31 +3,33 @@
"version": "1.0.0", "version": "1.0.0",
"description": "Dynamically generate stats for your github readmes", "description": "Dynamically generate stats for your github readmes",
"main": "index.js", "main": "index.js",
"type": "module",
"scripts": { "scripts": {
"test": "jest --coverage", "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
"test:watch": "jest --watch", "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",
"theme-readme-gen": "node scripts/generate-theme-doc", "theme-readme-gen": "node scripts/generate-theme-doc",
"preview-theme": "node scripts/preview-theme", "preview-theme": "node scripts/preview-theme",
"close-stale-theme-prs": "node scripts/close-stale-theme-prs",
"generate-langs-json": "node scripts/generate-langs-json", "generate-langs-json": "node scripts/generate-langs-json",
"format": "./node_modules/.bin/prettier --write .", "format": "./node_modules/.bin/prettier --write .",
"format:check": "./node_modules/.bin/prettier --check ." "format:check": "./node_modules/.bin/prettier --check ."
}, },
"author": "Anurag Hazra", "author": "Anurag Hazra",
"license": "MIT", "license": "MIT",
"engines": {
"node": ">=13"
},
"devDependencies": { "devDependencies": {
"@actions/core": "^1.2.4", "@actions/core": "^1.9.1",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"@testing-library/dom": "^7.20.0", "@testing-library/dom": "^8.17.1",
"@testing-library/jest-dom": "^5.11.0", "@testing-library/jest-dom": "^5.16.5",
"@uppercod/css-to-object": "^1.1.1", "@uppercod/css-to-object": "^1.1.1",
"axios-mock-adapter": "^1.18.1", "axios-mock-adapter": "^1.18.1",
"color-contrast-checker": "^2.1.0", "color-contrast-checker": "^2.1.0",
"hjson": "^3.2.2", "hjson": "^3.2.2",
"husky": "^4.2.5", "husky": "^4.2.5",
"jest": "^26.1.0", "jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash.snakecase": "^4.1.1", "lodash.snakecase": "^4.1.1",
"parse-diff": "^0.7.0", "parse-diff": "^0.7.0",
@@ -38,6 +40,7 @@
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"emoji-name-map": "^1.2.8", "emoji-name-map": "^1.2.8",
"github-username-regex": "^1.0.0", "github-username-regex": "^1.0.0",
"upgrade": "^1.1.0",
"word-wrap": "^1.2.3" "word-wrap": "^1.2.3"
}, },
"husky": { "husky": {
+30 -25
View File
@@ -67,12 +67,12 @@
<img src="https://d2wvdrxmr8p0wf.cloudfront.net/static/giveindia.svg" alt="Give india logo" width="200" /> <img src="https://d2wvdrxmr8p0wf.cloudfront.net/static/giveindia.svg" alt="Give india logo" width="200" />
</a> </a>
Are you considering supporting the project by donating? Please DON'T!! Are you considering supporting the project by donating? Please DON'T!!
Instead, Help India fight the 2nd deadly wave of COVID-19. Instead, Help India fight the 2nd deadly wave of COVID-19.
Thousands of people are dying in India because of a lack of Oxygen & also COVID-related infrastructure. Thousands of people are dying in India because of a lack of Oxygen & also COVID-related infrastructure.
Visit [https://indiafightscorona.giveindia.org](https://indiafightscorona.giveindia.org) and make a small donation to help us fight COVID and overcome this crisis. Visit [https://indiafightscorona.giveindia.org](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: A small donation goes a long way. :heart:
</p> </p>
@@ -160,18 +160,18 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you
#### Common Options: #### Common Options:
- `title_color` - Card's title color _(hex color)_ - `title_color` - Card's title color _(hex color)_. Default: `2f80ed`.
- `text_color` - Body text color _(hex color)_ - `text_color` - Body text color _(hex color)_. Default: `434d58`.
- `icon_color` - Icons color if available _(hex color)_ - `icon_color` - Icons color if available _(hex color)_. Default: `4c71f2`.
- `border_color` - Card's border color _(hex color)_. (Does not apply when `hide_border` is enabled) - `border_color` - Card's border color _(hex color)_. Default: `e4e2e2` (Does not apply when `hide_border` is enabled).
- `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_ - `bg_color` - Card's background color _(hex color)_ **or** a gradient in the form of _angle,start,end_. Default: `fffefe`
- `hide_border` - Hides the card's border _(boolean)_ - `hide_border` - Hides the card's border _(boolean)_. Default: `false`
- `theme` - name of the theme, choose from [all available themes](./themes/README.md) - `theme` - name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme.
- `cache_seconds` - set the cache header manually _(min: 7200, max: 86400)_ - `cache_seconds` - set the cache header manually _(min: 7200, max: 86400)_. Default: `14400 seconds (4 hours)`.
- `locale` - set the language in the card _(e.g. cn, de, es, etc.)_ - `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`.
- `border_radius` - Corner rounding on the card - `border_radius` - Corner rounding on the card. Default: `4.5`.
> Note: The minimum of cache_seconds is currently 4 hours as a temporary fix for PATs exhaustion. > Note: The minimum of cache_seconds is currently 4 hours as a temporary fix for PATs exhaustion.
##### Gradient in bg_color ##### Gradient in bg_color
@@ -185,16 +185,21 @@ You can provide multiple comma-separated values in the bg_color option to render
#### Stats Card Exclusive Options: #### Stats Card Exclusive Options:
- `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_ - `hide` - Hides the [specified items](#hiding-individual-stats) from stats _(Comma-separated values)_. Default: `[] (blank array)`.
- `hide_title` - _(boolean)_ - `hide_title` - _(boolean)_. Default: `false`.
- `card_width` - Set the card's width manually _(number)_ - `card_width` - Set the card's width manually _(number)_. Default: `500px (approx.)`.
- `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width - `hide_rank` - _(boolean)_ hides the rank and automatically resizes the card width. Default: `false`.
- `show_icons` - _(boolean)_ - `show_icons` - _(boolean)_. Default: `false`.
- `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_ - `include_all_commits` - Count total commits instead of just the current year commits _(boolean)_. Default: `false`.
- `count_private` - Count private commits _(boolean)_ - `count_private` - Count private commits _(boolean)_. Default: `false`.
- `line_height` - Sets the line-height between text _(number)_ - `line_height` - Sets the line-height between text _(number)_. Default: `25`.
- `custom_title` - Sets a custom title for the card - `exclude_repo` - Exclude stars from specified repositories _(Comma-separated values)_. Default: `[] (blank array)`.
- `disable_animations` - Disables all animations in the card _(boolean)_ - `custom_title` - Sets a custom title for the card. Default: `<username> Github Stats`.
- `text_bold` - Use bold text _(boolean)_. Default: `true`.
- `disable_animations` - Disables all animations in the card _(boolean)_. Default: `false`.
> 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:
+161
View File
@@ -0,0 +1,161 @@
/**
* @file Script that can be used to close stale theme PRs that have a `invalid` label.
*/
import * as dotenv from "dotenv";
dotenv.config();
import { debug, setFailed } from "@actions/core";
import github from "@actions/github";
import { RequestError } from "@octokit/request-error";
import { getGithubToken, getRepoInfo } from "./helpers.js";
// Script parameters
const CLOSING_COMMENT = `
\rThis PR has been automatically closed due to inactivity. Please feel free to reopen it if you need to continue working on it.\
\rThank you for your contributions.
`;
/**
* Fetch open PRs from a given repository.
* @param user The user name of the repository owner.
* @param repo The name of the repository.
* @returns The open PRs.
*/
export const fetchOpenPRs = async (octokit, user, repo) => {
const openPRs = [];
let hasNextPage = true;
let endCursor;
while (hasNextPage) {
try {
const { repository } = await octokit.graphql(
`
{
repository(owner: "${user}", name: "${repo}") {
open_prs: pullRequests(${
endCursor ? `after: "${endCursor}", ` : ""
}
first: 100, states: OPEN, orderBy: {field: CREATED_AT, direction: DESC}) {
nodes {
number
commits(last:1){
nodes{
commit{
pushedDate
}
}
}
labels(first: 100, orderBy:{field: CREATED_AT, direction: DESC}) {
nodes {
name
}
}
reviews(first: 1, states: CHANGES_REQUESTED, author: "github-actions[bot]") {
nodes {
updatedAt
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
`,
);
openPRs.push(...repository.open_prs.nodes);
hasNextPage = repository.open_prs.pageInfo.hasNextPage;
endCursor = repository.open_prs.pageInfo.endCursor;
} catch (error) {
if (error instanceof RequestError) {
setFailed(`Could not retrieve top PRs using GraphQl: ${error.message}`);
}
throw error;
}
}
return openPRs;
};
/**
* Retrieve pull requests that have a given label.
* @param pull The pull requests to check.
* @param label The label to check for.
*/
export const pullsWithLabel = (pulls, label) => {
return pulls.filter((pr) => {
return pr.labels.nodes.some((lab) => lab.name === label);
});
};
/**
* Check if PR is stale. Meaning that it hasn't been updated in a given time.
* @param {Object} pullRequest request object.
* @param {number} days number of days.
* @returns Boolean indicating if PR is stale.
*/
const isStale = (pullRequest, staleDays) => {
const lastCommitDate = new Date(
pullRequest.commits.nodes[0].commit.pushedDate,
);
if (pullRequest.reviews.nodes[0]) {
const lastReviewDate = new Date(pullRequest.reviews.nodes[0].updatedAt);
const lastUpdateDate =
lastCommitDate >= lastReviewDate ? lastCommitDate : lastReviewDate;
const now = new Date();
return now - lastUpdateDate > 1000 * 60 * 60 * 24 * staleDays;
} else {
return false;
}
};
/**
* Main function.
*/
const run = async () => {
try {
// Create octokit client.
const dryRun = process.env.DRY_RUN === "true" || false;
const staleDays = process.env.STALE_DAYS || 15;
debug("Creating octokit client...");
const octokit = github.getOctokit(getGithubToken());
const { owner, repo } = getRepoInfo(github.context);
// Retrieve all theme pull requests.
debug("Retrieving all theme pull requests...");
const prs = await fetchOpenPRs(octokit, owner, repo);
const themePRs = pullsWithLabel(prs, "themes");
const invalidThemePRs = pullsWithLabel(themePRs, "invalid");
debug("Retrieving stale theme PRs...");
const staleThemePRs = invalidThemePRs.filter((pr) =>
isStale(pr, staleDays),
);
const staleThemePRsNumbers = staleThemePRs.map((pr) => pr.number);
debug(`Found ${staleThemePRs.length} stale theme PRs`);
// Loop through all stale invalid theme pull requests and close them.
for (const prNumber of staleThemePRsNumbers) {
debug(`Closing #${prNumber} because it is stale...`);
if (!dryRun) {
await octokit.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: CLOSING_COMMENT,
});
await octokit.pulls.update({
owner,
repo,
pull_number: prNumber,
state: "closed",
});
} else {
debug("Dry run enabled, skipping...");
}
}
} catch (error) {
setFailed(error.message);
}
};
run();
+3 -3
View File
@@ -1,6 +1,6 @@
const fs = require("fs"); import axios from "axios";
const jsYaml = require("js-yaml"); import fs from "fs";
const axios = require("axios"); import jsYaml from "js-yaml";
const LANGS_FILEPATH = "./src/common/languageColors.json"; const LANGS_FILEPATH = "./src/common/languageColors.json";
+9 -9
View File
@@ -1,5 +1,5 @@
const theme = require("../themes/index"); import fs from "fs";
const fs = require("fs"); import { themes } from "../themes/index.js";
const TARGET_FILE = "./themes/README.md"; const TARGET_FILE = "./themes/README.md";
const REPO_CARD_LINKS_FLAG = "<!-- REPO_CARD_LINKS -->"; const REPO_CARD_LINKS_FLAG = "<!-- REPO_CARD_LINKS -->";
@@ -54,7 +54,7 @@ const createStatMdLink = (theme) => {
}; };
const generateLinks = (fn) => { const generateLinks = (fn) => {
return Object.keys(theme) return Object.keys(themes)
.map((name) => fn(name)) .map((name) => fn(name))
.join(""); .join("");
}; };
@@ -65,14 +65,14 @@ const createTableItem = ({ link, label, isRepoCard }) => {
}; };
const generateTable = ({ isRepoCard }) => { const generateTable = ({ isRepoCard }) => {
const rows = []; const rows = [];
const themes = Object.keys(theme).filter( const themesFiltered = Object.keys(themes).filter(
(name) => name !== (!isRepoCard ? "default_repocard" : "default"), (name) => name !== (!isRepoCard ? "default_repocard" : "default"),
); );
for (let i = 0; i < themes.length; i += 3) { for (let i = 0; i < themesFiltered.length; i += 3) {
const one = themes[i]; const one = themesFiltered[i];
const two = themes[i + 1]; const two = themesFiltered[i + 1];
const three = themes[i + 2]; const three = themesFiltered[i + 2];
let tableItem1 = createTableItem({ link: one, label: one, isRepoCard }); let tableItem1 = createTableItem({ link: one, label: one, isRepoCard });
let tableItem2 = createTableItem({ link: two, label: two, isRepoCard }); let tableItem2 = createTableItem({ link: two, label: two, isRepoCard });
@@ -84,7 +84,7 @@ const generateTable = ({ isRepoCard }) => {
rows.push(`| ${tableItem1} | ${tableItem2} | ${tableItem3} |`); rows.push(`| ${tableItem1} | ${tableItem2} | ${tableItem3} |`);
// if it's the last row & the row has no empty space push a new row // if it's the last row & the row has no empty space push a new row
if (three && i + 3 === themes.length) { if (three && i + 3 === themesFiltered.length) {
rows.push(`| [Add your theme][add-theme] | | |`); rows.push(`| [Add your theme][add-theme] | | |`);
} }
} }
+42
View File
@@ -0,0 +1,42 @@
/**
* @file Contains helper functions used in the scripts.
*/
import { getInput } from "@actions/core";
// Script variables.
const OWNER = "anuraghazra";
const REPO = "github-readme-stats";
/**
* Retrieve information about the repository that ran the action.
*
* @param {Object} context Action context.
* @returns {Object} Repository information.
*/
export const getRepoInfo = (ctx) => {
try {
return {
owner: ctx.repo.owner,
repo: ctx.repo.repo,
};
} catch (error) {
return {
owner: OWNER,
repo: REPO,
};
}
};
/**
* Retrieve github token and throw error if it is not found.
*
* @returns {string} Github token.
*/
export const getGithubToken = () => {
const token = getInput("github_token") || process.env.GITHUB_TOKEN;
if (!token) {
throw Error("Could not find github token");
}
return token;
};
+494 -115
View File
@@ -1,26 +1,81 @@
const core = require("@actions/core"); /**
const github = require("@actions/github"); * @file This script is used to preview the theme on theme PRs.
const parse = require("parse-diff"); */
const Hjson = require("hjson"); import * as dotenv from "dotenv";
const snakeCase = require("lodash.snakecase"); dotenv.config();
const ColorContrastChecker = require("color-contrast-checker");
require("dotenv").config(); import { debug, setFailed } from "@actions/core";
import github from "@actions/github";
import ColorContrastChecker from "color-contrast-checker";
import { info } from "console";
import Hjson from "hjson";
import snakeCase from "lodash.snakecase";
import parse from "parse-diff";
import { inspect } from "util";
import { isValidHexColor } from "../src/common/utils.js";
import { themes } from "../themes/index.js";
import { getGithubToken, getRepoInfo } from "./helpers.js";
// Script variables.
const COMMENTER = "github-actions[bot]";
const OWNER = "anuraghazra";
const REPO = "github-readme-stats";
const COMMENT_TITLE = "Automated Theme Preview"; const COMMENT_TITLE = "Automated Theme Preview";
const THEME_PR_FAIL_TEXT = ":x: Theme PR does not adhere to our guidelines.";
const THEME_PR_SUCCESS_TEXT =
":heavy_check_mark: Theme PR does adhere to our guidelines.";
const FAIL_TEXT = `
\rUnfortunately, your theme PR does not adhere to our [theme guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). Please fix the issues below, and we will review your\
\r PR again. This pull request will **automatically close in 15 days** if no changes are made. After this time, you must re-open the PR for it to be reviewed.
`;
const THEME_CONTRIB_GUIDELINESS = `
\rHi, thanks for the theme contribution. Please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution).
\rWe are currently only accepting color combinations from any VSCode theme or themes with good colour combinations to minimize bloating the themes collection.
\r> Also, note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection, you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization).
`;
const COLOR_PROPS = {
title_color: 6,
icon_color: 6,
text_color: 6,
bg_color: 8,
border_color: 6,
};
const ACCEPTED_COLOR_PROPS = Object.keys(COLOR_PROPS);
const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4);
const INVALID_REVIEW_COMMENT = (commentUrl) =>
`Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`;
/**
* Retrieve PR number from the event payload.
*
* @returns {number} PR number.
*/
const getPrNumber = () => {
if (process.env.MOCK_PR_NUMBER) return process.env.MOCK_PR_NUMBER; // For testing purposes.
function getPrNumber() {
const pullRequest = github.context.payload.pull_request; const pullRequest = github.context.payload.pull_request;
if (!pullRequest) { if (!pullRequest) {
return undefined; throw Error("Could not get pull request number from context");
} }
return pullRequest.number; return pullRequest.number;
} };
function findCommentPredicate(inputs, comment) { /**
* Retrieve the commenting user.
* @returns {string} Commenting user.
*/
const getCommenter = () => {
return process.env.COMMENTER ? process.env.COMMENTER : COMMENTER;
};
/**
* Returns whether the comment is a preview comment.
*
* @param {Object} inputs Action inputs.
* @param {Object} comment Comment object.
* @returns {boolean} Whether the comment is a preview comment.
*/
const isPreviewComment = (inputs, comment) => {
return ( return (
(inputs.commentAuthor && comment.user (inputs.commentAuthor && comment.user
? comment.user.login === inputs.commentAuthor ? comment.user.login === inputs.commentAuthor
@@ -29,160 +84,484 @@ function findCommentPredicate(inputs, comment) {
? comment.body.includes(inputs.bodyIncludes) ? comment.body.includes(inputs.bodyIncludes)
: true) : true)
); );
} };
async function findComment(octokit, issueNumber) { /**
* Find the preview theme comment.
*
* @param {Object} octokit Octokit instance.
* @param {number} issueNumber Issue number.
* @param {string} repo Repository name.
* @param {string} owner Owner of the repository.
* @returns {Object} The Github comment object.
*/
const findComment = async (octokit, issueNumber, owner, repo, commenter) => {
const parameters = { const parameters = {
owner: OWNER, owner,
repo: REPO, repo,
issue_number: issueNumber, issue_number: issueNumber,
}; };
const inputs = { const inputs = {
commentAuthor: OWNER, commentAuthor: commenter,
bodyIncludes: COMMENT_TITLE, bodyIncludes: COMMENT_TITLE,
}; };
// Search each page for the comment
for await (const { data: comments } of octokit.paginate.iterator( for await (const { data: comments } of octokit.paginate.iterator(
octokit.rest.issues.listComments, octokit.rest.issues.listComments,
parameters, parameters,
)) { )) {
// Search each page for the comment
const comment = comments.find((comment) => const comment = comments.find((comment) =>
findCommentPredicate(inputs, comment), isPreviewComment(inputs, comment),
); );
if (comment) return comment; if (comment) {
debug(`Found theme preview comment: ${inspect(comment)}`);
return comment;
} else {
debug(`No theme preview comment found.`);
}
} }
} };
async function upsertComment(octokit, props) { /**
* Create or update the preview comment.
*
* @param {Object} octokit Octokit instance.
* @param {Object} props Comment properties.
* @return {string} The comment URL.
*/
const upsertComment = async (octokit, props) => {
let resp;
if (props.comment_id !== undefined) { if (props.comment_id !== undefined) {
await octokit.issues.updateComment(props); resp = await octokit.issues.updateComment(props);
} else { } else {
await octokit.issues.createComment(props); resp = await octokit.issues.createComment(props);
} }
} return resp.data.html_url;
};
function getWebAimLink(color1, color2) { /**
* Adds a review to the pull request.
*
* @param {Object} octokit Octokit instance.
* @param {number} prNumber Pull request number.
* @param {string} owner Owner of the repository.
* @param {string} repo Repository name.
* @param {string} reviewState The review state. Options are (APPROVE, REQUEST_CHANGES, COMMENT, PENDING).
* @param {string} reason The reason for the review.
*/
const addReview = async (
octokit,
prNumber,
owner,
repo,
reviewState,
reason,
) => {
await octokit.pulls.createReview({
owner,
repo,
pull_number: prNumber,
event: reviewState,
body: reason,
});
};
/**
* Add label to pull request.
*
* @param {Object} octokit Octokit instance.
* @param {number} prNumber Pull request number.
* @param {string} owner Repository owner.
* @param {string} repo Repository name.
* @param {string[]} labels Labels to add.
*/
const addLabel = async (octokit, prNumber, owner, repo, labels) => {
await octokit.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels,
});
};
/**
* Remove label from the pull request.
*
* @param {Object} octokit Octokit instance.
* @param {number} prNumber Pull request number.
* @param {string} owner Repository owner.
* @param {string} repo Repository name.
* @param {string} label Label to add or remove.
*/
const removeLabel = async (octokit, prNumber, owner, repo, label) => {
await octokit.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: label,
});
};
/**
* Adds or removes a label from the pull request.
*
* @param {Object} octokit Octokit instance.
* @param {number} prNumber Pull request number.
* @param {string} owner Repository owner.
* @param {string} repo Repository name.
* @param {string} label Label to add or remove.
* @param {boolean} add Whether to add or remove the label.
*/
const addRemoveLabel = async (octokit, prNumber, owner, repo, label, add) => {
const res = await octokit.pulls.get({
owner,
repo,
pull_number: prNumber,
});
if (add) {
if (!res.data.labels.find((l) => l.name === label)) {
await addLabel(octokit, prNumber, owner, repo, [label]);
}
} else {
if (res.data.labels.find((l) => l.name === label)) {
await removeLabel(octokit, prNumber, owner, repo, label);
}
}
};
/**
* Retrieve webAim contrast color check link.
*
* @param {string} color1 First color.
* @param {string} color2 Second color.
* @returns {string} WebAim contrast color check link.
*/
const getWebAimLink = (color1, color2) => {
return `https://webaim.org/resources/contrastchecker/?fcolor=${color1}&bcolor=${color2}`; return `https://webaim.org/resources/contrastchecker/?fcolor=${color1}&bcolor=${color2}`;
} };
function getGrsLink(colors) { /**
* Retrieves the theme GRS url.
*
* @param {Object} colors The theme colors.
* @returns {string} GRS theme url.
*/
const getGRSLink = (colors) => {
const url = `https://github-readme-stats.vercel.app/api?username=anuraghazra`; const url = `https://github-readme-stats.vercel.app/api?username=anuraghazra`;
const colorString = Object.keys(colors) const colorString = Object.keys(colors)
.map((colorKey) => `${colorKey}=${colors[colorKey]}`) .map((colorKey) => `${colorKey}=${colors[colorKey]}`)
.join("&"); .join("&");
return `${url}&${colorString}&show_icons=true`; return `${url}&${colorString}&show_icons=true`;
} };
const themeContribGuidelines = ` /**
\rHi, thanks for the theme contribution, please read our theme [contribution guidelines](https://github.com/anuraghazra/github-readme-stats/blob/master/CONTRIBUTING.md#themes-contribution). * Retrieve javascript object from json string.
\rWe are currently only accepting color combinations from any VSCode theme or themes which have good color combination to minimize bloating the themes collection. *
* @description Wraps the Hjson parse function to fix several known json syntax errors.
\r> Also note that if this theme is exclusively for your personal use, then instead of adding it to our theme collection you can use card [customization options](https://github.com/anuraghazra/github-readme-stats#customization) *
`; * @param {string} json The json to parse.
* @returns {Object} Object parsed from the json.
async function run() { */
const parseJSON = (json) => {
try { try {
const ccc = new ColorContrastChecker(); const parsedJson = Hjson.parse(json);
const warnings = []; if (typeof parsedJson === "object") {
const token = core.getInput("token"); return parsedJson;
const octokit = github.getOctokit(token || process.env.PERSONAL_TOKEN); } else {
const pullRequestId = getPrNumber(); throw new Error("PR diff is not a valid theme JSON object.");
if (!pullRequestId) {
console.log("PR not found");
return;
} }
} catch (error) {
let parsedJson = json
.split(/([\s\r\s]*}[\s\r\s]*,[\s\r\s]*)(?=[\w"-]+:)/)
.filter((x) => typeof x !== "string" || !!x.trim());
if (parsedJson[0].replace(/\s+/g, "") === "},") {
parsedJson[0] = "},";
if (!/\s*}\s*,?\s*$/.test(parsedJson[1])) {
parsedJson.push(parsedJson.shift());
} else {
parsedJson.shift();
}
return Hjson.parse(parsedJson.join(""));
} else {
throw error;
}
}
};
/**
* Check whether the theme name is still available.
* @param {string} name Theme name.
* @returns {boolean} Whether the theme name is available.
*/
const themeNameAlreadyExists = (name) => {
return themes[name] !== undefined;
};
/**
* Main function.
*/
export const run = async (prNumber) => {
try {
const dryRun = process.env.DRY_RUN === "true" || false;
debug("Retrieve action information from context...");
debug(`Context: ${inspect(github.context)}`);
let commentBody = `
\r# ${COMMENT_TITLE}
\r${THEME_CONTRIB_GUIDELINESS}
`;
const ccc = new ColorContrastChecker();
const octokit = github.getOctokit(getGithubToken());
const pullRequestId = prNumber ? prNumber : getPrNumber();
const commenter = getCommenter();
const { owner, repo } = getRepoInfo(github.context);
debug(`Owner: ${owner}`);
debug(`Repo: ${repo}`);
debug(`Commenter: ${commenter}`);
// Retrieve the PR diff and preview-theme comment.
debug("Retrieve PR diff...");
const res = await octokit.pulls.get({ const res = await octokit.pulls.get({
owner: OWNER, owner,
repo: REPO, repo,
pull_number: pullRequestId, pull_number: pullRequestId,
mediaType: { mediaType: {
format: "diff", format: "diff",
}, },
}); });
const comment = await findComment(octokit, pullRequestId); debug("Retrieve preview-theme comment...");
const comment = await findComment(
octokit,
pullRequestId,
owner,
repo,
commenter,
);
// Retrieve theme changes from the PR diff.
debug("Retrieve themes...");
const diff = parse(res.data); const diff = parse(res.data);
const content = diff const content = diff
.find((file) => file.to === "themes/index.js") .find((file) => file.to === "themes/index.js")
.chunks[0].changes.filter((c) => c.type === "add") .chunks[0].changes.filter((c) => c.type === "add")
.map((c) => c.content.replace("+", "")) .map((c) => c.content.replace("+", ""))
.join(""); .join("");
const themeObject = parseJSON(content);
const themeObject = Hjson.parse(content); if (
const themeName = Object.keys(themeObject)[0]; Object.keys(themeObject).every(
const colors = themeObject[themeName]; (key) => typeof themeObject[key] !== "object",
)
if (themeName !== snakeCase(themeName)) { ) {
warnings.push("Theme name isn't in snake_case"); throw new Error("PR diff is not a valid theme JSON object.");
} }
if (!colors) { // Loop through themes and create theme preview body.
await upsertComment({ debug("Create theme preview body...");
comment_id: comment?.id, const themeValid = Object.fromEntries(
owner: OWNER, Object.keys(themeObject).map((name) => [name, true]),
repo: REPO, );
issue_number: pullRequestId, let previewBody = "";
body: ` for (const theme in themeObject) {
\r**${COMMENT_TITLE}** debug(`Create theme preview for ${theme}...`);
const themeName = theme;
\rCannot create theme preview const colors = themeObject[theme];
const warnings = [];
const errors = [];
${themeContribGuidelines} // Check if the theme name is valid.
`, debug("Theme preview body: Check if the theme name is valid...");
}); if (themeNameAlreadyExists(themeName)) {
return; warnings.push("Theme name already taken");
} themeValid[theme] = false;
}
const titleColor = colors.title_color; if (themeName !== snakeCase(themeName)) {
const iconColor = colors.icon_color; warnings.push("Theme name isn't in snake_case");
const textColor = colors.text_color; themeValid[theme] = false;
const bgColor = colors.bg_color;
const url = getGrsLink(colors);
const colorPairs = {
title_color: [titleColor, bgColor],
icon_color: [iconColor, bgColor],
text_color: [textColor, bgColor],
};
// check color contrast
Object.keys(colorPairs).forEach((key) => {
const color1 = colorPairs[key][0];
const color2 = colorPairs[key][1];
if (!ccc.isLevelAA(`#${color1}`, `#${color2}`)) {
const permalink = getWebAimLink(color1, color2);
warnings.push(
`\`${key}\` does not pass [AA contrast ratio](${permalink})`,
);
} }
});
await upsertComment(octokit, { // Check if the theme colors are valid.
comment_id: comment?.id, debug("Theme preview body: Check if the theme colors are valid...");
issue_number: pullRequestId, let invalidColors = false;
owner: OWNER, if (!colors) {
repo: REPO, warning.push("Theme colors are missing");
body: ` invalidColors = true;
\r**${COMMENT_TITLE}** } else {
const missingKeys = REQUIRED_COLOR_PROPS.filter(
\r${warnings.map((warning) => `- :warning: ${warning}\n`).join("")} (x) => !Object.keys(colors).includes(x),
);
const extraKeys = Object.keys(colors).filter(
(x) => !ACCEPTED_COLOR_PROPS.includes(x),
);
if (missingKeys.length > 0 || extraKeys.length > 0) {
for (const missingKey of missingKeys) {
errors.push(`Theme color properties \`${missingKey}\` are missing`);
}
\ntitle_color: <code>#${titleColor}</code> | icon_color: <code>#${iconColor}</code> | text_color: <code>#${textColor}</code> | bg_color: <code>#${bgColor}</code> for (const extraKey of extraKeys) {
warnings.push(
\r[Preview Link](${url}) `Theme color properties \`${extraKey}\` is not supported`,
);
}
invalidColors = true;
} else {
for (const [colorKey, colorValue] of Object.entries(colors)) {
if (colorValue[0] === "#") {
errors.push(
`Theme color property \`${colorKey}\` should not start with '#'`,
);
invalidColors = true;
} else if (colorValue.length > COLOR_PROPS[colorKey]) {
errors.push(
`Theme color property \`${colorKey}\` can not be longer than \`${COLOR_PROPS[colorKey]}\` characters`,
);
invalidColors = true;
} else if (!isValidHexColor(colorValue)) {
errors.push(
`Theme color property \`${colorKey}\` is not a valid hex color: <code>#${colorValue}</code>`,
);
invalidColors = true;
}
}
}
}
if (invalidColors) {
themeValid[theme] = false;
previewBody += `
\r### ${
themeName.charAt(0).toUpperCase() + themeName.slice(1)
} theme preview
\r${warnings.map((warning) => `- :warning: ${warning}.\n`).join("")}
\r${errors.map((error) => `- :x: ${error}.\n`).join("")}
\r[![](${url})](${url}) \r>:x: Cannot create theme preview.
`;
continue;
}
// Check color contrast.
debug("Theme preview body: Check color contrast...");
const titleColor = colors.title_color;
const iconColor = colors.icon_color;
const textColor = colors.text_color;
const bgColor = colors.bg_color;
const borderColor = colors.border_color;
const url = getGRSLink(colors);
const colorPairs = {
title_color: [titleColor, bgColor],
icon_color: [iconColor, bgColor],
text_color: [textColor, bgColor],
};
Object.keys(colorPairs).forEach((item) => {
let color1 = colorPairs[item][0];
let color2 = colorPairs[item][1];
color1 = color1.length === 4 ? color1.slice(0, 3) : color1.slice(0, 6);
color2 = color2.length === 4 ? color2.slice(0, 3) : color2.slice(0, 6);
if (!ccc.isLevelAA(`#${color1}`, `#${color2}`)) {
const permalink = getWebAimLink(color1, color2);
warnings.push(
`\`${item}\` does not pass [AA contrast ratio](${permalink})`,
);
themeValid[theme] = false;
}
});
// Create theme preview body.
debug("Theme preview body: Create theme preview body...");
previewBody += `
\r### ${
themeName.charAt(0).toUpperCase() + themeName.slice(1)
} theme preview
\r${warnings.map((warning) => `- :warning: ${warning}.\n`).join("")}
\ntitle_color: <code>#${titleColor}</code> | icon_color: <code>#${iconColor}</code> | text_color: <code>#${textColor}</code> | bg_color: <code>#${bgColor}</code>${
borderColor ? ` | border_color: <code>#${borderColor}</code>` : ""
}
\r[Preview Link](${url})
\r[![](${url})](${url})
`;
}
// Create comment body.
debug("Create comment body...");
commentBody += `
\r${
Object.values(themeValid).every((value) => value)
? THEME_PR_SUCCESS_TEXT
: THEME_PR_FAIL_TEXT
}
\r## Test results
\r${Object.entries(themeValid)
.map(
([key, value]) => `- ${value ? ":heavy_check_mark:" : ":x:"} ${key}`,
)
.join("\r")}
\r${
Object.values(themeValid).every((value) => value)
? "**Result:** :heavy_check_mark: All themes are valid."
: "**Result:** :x: Some themes are invalid.\n\n" + FAIL_TEXT
}
${themeContribGuidelines} \r## Details
`, \r${previewBody}
}); `;
// Create or update theme-preview comment.
debug("Create or update theme-preview comment...");
let comment_url;
if (!dryRun) {
comment_url = await upsertComment(octokit, {
comment_id: comment?.id,
issue_number: pullRequestId,
owner,
repo,
body: commentBody,
});
} else {
info(`DRY_RUN: Comment body: ${commentBody}`);
comment_url = "";
}
// Change review state and add/remove `invalid` label based on theme PR validity.
debug(
"Change review state and add/remove `invalid` label based on whether all themes passed...",
);
const themesValid = Object.values(themeValid).every((value) => value);
const reviewState = themesValid ? "APPROVE" : "REQUEST_CHANGES";
const reviewReason = themesValid
? undefined
: INVALID_REVIEW_COMMENT(comment_url);
if (!dryRun) {
await addReview(
octokit,
pullRequestId,
owner,
repo,
reviewState,
reviewReason,
);
await addRemoveLabel(
octokit,
pullRequestId,
owner,
repo,
"invalid",
!themesValid,
);
} else {
info(`DRY_RUN: Review state: ${reviewState}`);
info(`DRY_RUN: Review reason: ${reviewReason}`);
}
} catch (error) { } catch (error) {
console.log(error); setFailed(error.message);
} }
} };
run(); if (typeof require !== "undefined" && require.main === module) {
run();
}
+1 -1
View File
@@ -11,4 +11,4 @@ git checkout -b $BRANCH_NAME
git add --all git add --all
git commit --message "docs(theme): Auto update theme readme" || exit 0 git commit --message "docs(theme): Auto update theme readme" || exit 0
git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git
git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME
+54
View File
@@ -0,0 +1,54 @@
/**
* @file Apply preview-theme script to all theme pull requests.
*/
import { debug, setFailed } from "@actions/core";
import { getGithubToken, getRepoInfo } from "./helpers.js";
import { run as previewTheme } from "./preview-theme.js";
import github from "@actions/github";
import { fetchOpenPRs, pullsWithLabel } from "./close-stale-theme-prs.js";
/**
* Get all pull request that do not contain a given label.
* @param pull The pull requests to check.
* @param label The label to check for.
* @returns Whether the pull request contains the label.
*/
export const pullsWithoutLabel = (pulls, label) => {
return pulls.filter((pr) => {
return pr.labels.nodes.some((lab) => lab.name !== label);
});
};
/**
* Main function.
*/
const run = async () => {
try {
// Create octokit client.
const dryRun = process.env.DRY_RUN === "true" || false;
debug("Creating octokit client...");
const octokit = github.getOctokit(getGithubToken());
const { owner, repo } = getRepoInfo(github.context);
// Retrieve all theme pull requests.
debug("Retrieving all theme pull requests...");
const prs = await fetchOpenPRs(octokit, owner, repo);
let themePRs = pullsWithLabel(prs, "themes");
themePRs = pullsWithoutLabel(themePRs, "invalid");
debug(`Found ${themePRs.length} theme PRs`);
// Loop through all theme PR and execute the theme-preview script.
for (const pr of themePRs) {
debug(`Applying theme-preview to #${pr.number}...`);
console.log("Pull request number: ", pr.number);
if (!dryRun) {
await previewTheme(pr.number);
}
}
} catch (error) {
setFailed(error.message);
}
};
run();
+4 -3
View File
@@ -57,8 +57,8 @@ function calculateRank({
issues * ISSUES_OFFSET + issues * ISSUES_OFFSET +
stargazers * STARS_OFFSET + stargazers * STARS_OFFSET +
prs * PRS_OFFSET + prs * PRS_OFFSET +
followers * FOLLOWERS_OFFSET + followers * FOLLOWERS_OFFSET +
totalRepos * REPO_OFFSET totalRepos * REPO_OFFSET
) / 100; ) / 100;
const normalizedScore = normalcdf(score, TOTAL_VALUES, ALL_OFFSETS) * 100; const normalizedScore = normalcdf(score, TOTAL_VALUES, ALL_OFFSETS) * 100;
@@ -74,4 +74,5 @@ function calculateRank({
return { level, score: normalizedScore }; return { level, score: normalizedScore };
} }
module.exports = calculateRank; export { calculateRank };
export default calculateRank;
+11 -10
View File
@@ -1,17 +1,17 @@
// @ts-check // @ts-check
const { import { Card } from "../common/Card.js";
kFormatter, import { I18n } from "../common/I18n.js";
import { icons } from "../common/icons.js";
import {
encodeHTML, encodeHTML,
getCardColors,
flexLayout, flexLayout,
wrapTextMultiline, getCardColors,
kFormatter,
measureText, measureText,
parseEmojis, parseEmojis,
} = require("../common/utils"); wrapTextMultiline,
const I18n = require("../common/I18n"); } from "../common/utils.js";
const Card = require("../common/Card"); import { repoCardLocales } from "../translations.js";
const icons = require("../common/icons");
const { repoCardLocales } = require("../translations");
/** /**
* @param {string} label * @param {string} label
@@ -185,4 +185,5 @@ const renderRepoCard = (repo, options = {}) => {
`); `);
}; };
module.exports = renderRepoCard; export { renderRepoCard };
export default renderRepoCard;
+19 -13
View File
@@ -1,16 +1,16 @@
// @ts-check // @ts-check
const I18n = require("../common/I18n"); import { Card } from "../common/Card.js";
const Card = require("../common/Card"); import { I18n } from "../common/I18n.js";
const icons = require("../common/icons"); import { icons } from "../common/icons.js";
const { getStyles } = require("../getStyles"); import {
const { statCardLocales } = require("../translations");
const {
kFormatter,
flexLayout,
clampValue, clampValue,
measureText, flexLayout,
getCardColors, getCardColors,
} = require("../common/utils"); kFormatter,
measureText,
} from "../common/utils.js";
import { getStyles } from "../getStyles.js";
import { statCardLocales } from "../translations.js";
const createTextNode = ({ const createTextNode = ({
icon, icon,
@@ -20,6 +20,7 @@ const createTextNode = ({
index, index,
showIcons, showIcons,
shiftValuePos, shiftValuePos,
bold,
}) => { }) => {
const kValue = kFormatter(value); const kValue = kFormatter(value);
const staggerDelay = (index + 3) * 150; const staggerDelay = (index + 3) * 150;
@@ -35,9 +36,11 @@ const createTextNode = ({
return ` return `
<g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)"> <g class="stagger" style="animation-delay: ${staggerDelay}ms" transform="translate(25, 0)">
${iconSvg} ${iconSvg}
<text class="stat bold" ${labelOffset} y="12.5">${label}:</text> <text class="stat ${
bold ? " bold" : "not_bold"
}" ${labelOffset} y="12.5">${label}:</text>
<text <text
class="stat" class="stat ${bold ? " bold" : "not_bold"}"
x="${(showIcons ? 140 : 120) + shiftValuePos}" x="${(showIcons ? 140 : 120) + shiftValuePos}"
y="12.5" y="12.5"
data-testid="${id}" data-testid="${id}"
@@ -73,6 +76,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
title_color, title_color,
icon_color, icon_color,
text_color, text_color,
text_bold = true,
bg_color, bg_color,
theme = "default", theme = "default",
custom_title, custom_title,
@@ -165,6 +169,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
showIcons: show_icons, showIcons: show_icons,
shiftValuePos: shiftValuePos:
(!include_all_commits ? 50 : 35) + (isLongLocale ? 50 : 0), (!include_all_commits ? 50 : 35) + (isLongLocale ? 50 : 0),
bold: text_bold,
}), }),
); );
@@ -295,4 +300,5 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => {
`); `);
}; };
module.exports = renderStatsCard; export { renderStatsCard };
export default renderStatsCard;
+9 -10
View File
@@ -1,16 +1,16 @@
// @ts-check // @ts-check
const Card = require("../common/Card"); import { Card } from "../common/Card.js";
const I18n = require("../common/I18n"); import { createProgressNode } from "../common/createProgressNode.js";
const { langCardLocales } = require("../translations"); import { I18n } from "../common/I18n.js";
const { createProgressNode } = require("../common/createProgressNode"); import {
const { chunkArray,
clampValue, clampValue,
getCardColors,
flexLayout, flexLayout,
getCardColors,
lowercaseTrim, lowercaseTrim,
measureText, measureText,
chunkArray, } from "../common/utils.js";
} = require("../common/utils"); import { langCardLocales } from "../translations.js";
const DEFAULT_CARD_WIDTH = 300; const DEFAULT_CARD_WIDTH = 300;
const MIN_CARD_WIDTH = 230; const MIN_CARD_WIDTH = 230;
@@ -311,5 +311,4 @@ const renderTopLanguages = (topLangs, options = {}) => {
`); `);
}; };
module.exports = renderTopLanguages; export { renderTopLanguages, MIN_CARD_WIDTH };
module.exports.MIN_CARD_WIDTH = MIN_CARD_WIDTH;
+2 -1
View File
@@ -1,4 +1,4 @@
type ThemeNames = keyof typeof import("../../themes"); type ThemeNames = keyof typeof import("../../themes/index.js");
export type CommonOptions = { export type CommonOptions = {
title_color: string; title_color: string;
@@ -16,6 +16,7 @@ export type StatCardOptions = CommonOptions & {
show_icons: boolean; show_icons: boolean;
hide_title: boolean; hide_title: boolean;
hide_border: boolean; hide_border: boolean;
card_width: number;
hide_rank: boolean; hide_rank: boolean;
include_all_commits: boolean; include_all_commits: boolean;
line_height: number | string; line_height: number | string;
+21 -11
View File
@@ -1,16 +1,26 @@
// @ts-check // @ts-check
const Card = require("../common/Card"); import { Card } from "../common/Card.js";
const I18n = require("../common/I18n"); import { createProgressNode } from "../common/createProgressNode.js";
const { getStyles } = require("../getStyles"); import { I18n } from "../common/I18n.js";
const { wakatimeCardLocales } = require("../translations"); import {
const languageColors = require("../common/languageColors.json");
const { createProgressNode } = require("../common/createProgressNode");
const {
clampValue, clampValue,
getCardColors,
flexLayout, flexLayout,
getCardColors,
lowercaseTrim, lowercaseTrim,
} = require("../common/utils"); } from "../common/utils.js";
import { getStyles } from "../getStyles.js";
import { wakatimeCardLocales } from "../translations.js";
/** 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
/** /**
* @param {{color: string, text: string}} param0 * @param {{color: string, text: string}} param0
@@ -314,5 +324,5 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
`); `);
}; };
module.exports = renderWakatimeCard; export { renderWakatimeCard };
exports.createProgressNode = createProgressNode; export default renderWakatimeCard;
+5 -4
View File
@@ -1,5 +1,5 @@
const { getAnimations } = require("../getStyles"); import { getAnimations } from "../getStyles.js";
const { flexLayout, encodeHTML } = require("../common/utils"); import { encodeHTML, flexLayout } from "./utils.js";
class Card { class Card {
/** /**
@@ -10,7 +10,7 @@ class Card {
* @param {string?=} args.customTitle * @param {string?=} args.customTitle
* @param {string?=} args.defaultTitle * @param {string?=} args.defaultTitle
* @param {string?=} args.titlePrefixIcon * @param {string?=} args.titlePrefixIcon
* @param {ReturnType<import('../common/utils').getCardColors>?=} args.colors * @param {ReturnType<import('../common/utils.js').getCardColors>?=} args.colors
*/ */
constructor({ constructor({
width = 100, width = 100,
@@ -216,4 +216,5 @@ class Card {
} }
} }
module.exports = Card; export { Card };
export default Card;
+2 -1
View File
@@ -18,4 +18,5 @@ class I18n {
} }
} }
module.exports = I18n; export { I18n };
export default I18n;
+2 -1
View File
@@ -1,3 +1,4 @@
const blacklist = ["renovate-bot", "technote-space", "sw-yx"]; const blacklist = ["renovate-bot", "technote-space", "sw-yx"];
module.exports = blacklist; export { blacklist };
export default blacklist;
+4 -3
View File
@@ -1,4 +1,4 @@
const { clampValue } = require("../common/utils"); import { clampValue } from "./utils.js";
const createProgressNode = ({ const createProgressNode = ({
x, x,
@@ -16,7 +16,7 @@ const createProgressNode = ({
<rect <rect
height="8" height="8"
fill="${color}" fill="${color}"
rx="5" ry="5" x="0" y="0" rx="5" ry="5" x="0" y="0"
data-testid="lang-progress" data-testid="lang-progress"
width="${progressPercentage}%" width="${progressPercentage}%"
> >
@@ -25,4 +25,5 @@ const createProgressNode = ({
`; `;
}; };
exports.createProgressNode = createProgressNode; export { createProgressNode };
export default createProgressNode;
+2 -1
View File
@@ -8,4 +8,5 @@ const icons = {
fork: `<path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path>`, fork: `<path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"></path>`,
}; };
module.exports = icons; export { icons };
export default icons;
+31 -3
View File
@@ -15,6 +15,7 @@
"ATS": "#1ac620", "ATS": "#1ac620",
"ActionScript": "#882B0F", "ActionScript": "#882B0F",
"Ada": "#02f88c", "Ada": "#02f88c",
"Adblock Filter List": "#800000",
"Adobe Font Metrics": "#fa0f00", "Adobe Font Metrics": "#fa0f00",
"Agda": "#315665", "Agda": "#315665",
"Alloy": "#64C800", "Alloy": "#64C800",
@@ -22,6 +23,7 @@
"Altium Designer": "#A89663", "Altium Designer": "#A89663",
"AngelScript": "#C7D7DC", "AngelScript": "#C7D7DC",
"Ant Build System": "#A9157E", "Ant Build System": "#A9157E",
"Antlers": "#ff269e",
"ApacheConf": "#d12127", "ApacheConf": "#d12127",
"Apex": "#1797c0", "Apex": "#1797c0",
"Apollo Guidance Computer": "#0B3D91", "Apollo Guidance Computer": "#0B3D91",
@@ -41,8 +43,10 @@
"Ballerina": "#FF5000", "Ballerina": "#FF5000",
"Batchfile": "#C1F12E", "Batchfile": "#C1F12E",
"Beef": "#a52f4e", "Beef": "#a52f4e",
"Berry": "#15A13C",
"BibTeX": "#778899", "BibTeX": "#778899",
"Bicep": "#519aba", "Bicep": "#519aba",
"Bikeshed": "#5562ac",
"Bison": "#6A463F", "Bison": "#6A463F",
"BitBake": "#00bce4", "BitBake": "#00bce4",
"Blade": "#f7523f", "Blade": "#f7523f",
@@ -52,11 +56,13 @@
"Boo": "#d4bec1", "Boo": "#d4bec1",
"Boogie": "#c80fa0", "Boogie": "#c80fa0",
"Brainfuck": "#2F2530", "Brainfuck": "#2F2530",
"BrighterScript": "#66AABB",
"Brightscript": "#662D91", "Brightscript": "#662D91",
"Browserslist": "#ffd539", "Browserslist": "#ffd539",
"C": "#555555", "C": "#555555",
"C#": "#178600", "C#": "#178600",
"C++": "#f34b7d", "C++": "#f34b7d",
"CAP CDS": "#0092d1",
"CLIPS": "#00A300", "CLIPS": "#00A300",
"CMake": "#DA3434", "CMake": "#DA3434",
"COLLADA": "#F1A42B", "COLLADA": "#F1A42B",
@@ -66,7 +72,9 @@
"CUE": "#5886E1", "CUE": "#5886E1",
"CWeb": "#00007a", "CWeb": "#00007a",
"Cabal Config": "#483465", "Cabal Config": "#483465",
"Cadence": "#00ef8b",
"Cairo": "#ff4a48", "Cairo": "#ff4a48",
"CameLIGO": "#3be133",
"Cap'n Proto": "#c42727", "Cap'n Proto": "#c42727",
"Ceylon": "#dfa535", "Ceylon": "#dfa535",
"Chapel": "#8dc63f", "Chapel": "#8dc63f",
@@ -120,6 +128,7 @@
"Eiffel": "#4d6977", "Eiffel": "#4d6977",
"Elixir": "#6e4a7e", "Elixir": "#6e4a7e",
"Elm": "#60B5CC", "Elm": "#60B5CC",
"Elvish": "#55BB55",
"Emacs Lisp": "#c065db", "Emacs Lisp": "#c065db",
"EmberScript": "#FFF4F3", "EmberScript": "#FFF4F3",
"Erlang": "#B83998", "Erlang": "#B83998",
@@ -153,6 +162,9 @@
"GSC": "#FF6800", "GSC": "#FF6800",
"Game Maker Language": "#71b417", "Game Maker Language": "#71b417",
"Gemfile.lock": "#701516", "Gemfile.lock": "#701516",
"Gemini": "#ff6900",
"Genero": "#63408e",
"Genero Forms": "#d8df39",
"Genie": "#fb855d", "Genie": "#fb855d",
"Genshi": "#951531", "Genshi": "#951531",
"Gentoo Ebuild": "#9400ff", "Gentoo Ebuild": "#9400ff",
@@ -161,6 +173,7 @@
"Gherkin": "#5B2063", "Gherkin": "#5B2063",
"Git Attributes": "#F44D27", "Git Attributes": "#F44D27",
"Git Config": "#F44D27", "Git Config": "#F44D27",
"Git Revision List": "#F44D27",
"Gleam": "#ffaff3", "Gleam": "#ffaff3",
"Glyph": "#c1ac7f", "Glyph": "#c1ac7f",
"Gnuplot": "#f0a9f0", "Gnuplot": "#f0a9f0",
@@ -178,6 +191,7 @@
"Groovy Server Pages": "#4298b8", "Groovy Server Pages": "#4298b8",
"HAProxy": "#106da9", "HAProxy": "#106da9",
"HLSL": "#aace60", "HLSL": "#aace60",
"HOCON": "#9ff8ee",
"HTML": "#e34c26", "HTML": "#e34c26",
"HTML+ECR": "#2e1052", "HTML+ECR": "#2e1052",
"HTML+EEX": "#6e4a7e", "HTML+EEX": "#6e4a7e",
@@ -222,6 +236,7 @@
"JavaScript": "#f1e05a", "JavaScript": "#f1e05a",
"JavaScript+ERB": "#f1e05a", "JavaScript+ERB": "#f1e05a",
"Jest Snapshot": "#15c213", "Jest Snapshot": "#15c213",
"JetBrains MPS": "#21D789",
"Jinja": "#a52a22", "Jinja": "#a52a22",
"Jison": "#56b3cb", "Jison": "#56b3cb",
"Jison Lex": "#56b3cb", "Jison Lex": "#56b3cb",
@@ -246,6 +261,7 @@
"Latte": "#f2a542", "Latte": "#f2a542",
"Less": "#1d365d", "Less": "#1d365d",
"Lex": "#DBCA00", "Lex": "#DBCA00",
"LigoLANG": "#0e74ff",
"LilyPond": "#9ccc7c", "LilyPond": "#9ccc7c",
"Liquid": "#67b8de", "Liquid": "#67b8de",
"Literate Agda": "#315665", "Literate Agda": "#315665",
@@ -278,13 +294,16 @@
"Modelica": "#de1d31", "Modelica": "#de1d31",
"Modula-2": "#10253f", "Modula-2": "#10253f",
"Modula-3": "#223388", "Modula-3": "#223388",
"Monkey C": "#8D6747",
"MoonScript": "#ff4585", "MoonScript": "#ff4585",
"Motoko": "#fbb03b", "Motoko": "#fbb03b",
"Motorola 68K Assembly": "#005daa", "Motorola 68K Assembly": "#005daa",
"Move": "#4a137a",
"Mustache": "#724b3b", "Mustache": "#724b3b",
"NCL": "#28431f", "NCL": "#28431f",
"NPM Config": "#cb3837", "NPM Config": "#cb3837",
"NWScript": "#111522", "NWScript": "#111522",
"Nasal": "#1d2c4e",
"Nearley": "#990000", "Nearley": "#990000",
"Nemerle": "#3d3c6e", "Nemerle": "#3d3c6e",
"NetLinx": "#0aa0ff", "NetLinx": "#0aa0ff",
@@ -332,6 +351,7 @@
"PigLatin": "#fcd7de", "PigLatin": "#fcd7de",
"Pike": "#005390", "Pike": "#005390",
"PogoScript": "#d80074", "PogoScript": "#d80074",
"Portugol": "#f8bd00",
"PostCSS": "#dc3a0c", "PostCSS": "#dc3a0c",
"PostScript": "#da291c", "PostScript": "#da291c",
"PowerBuilder": "#8f0f8d", "PowerBuilder": "#8f0f8d",
@@ -366,6 +386,7 @@
"Rascal": "#fffaa0", "Rascal": "#fffaa0",
"ReScript": "#ed5051", "ReScript": "#ed5051",
"Reason": "#ff5847", "Reason": "#ff5847",
"ReasonLIGO": "#ff5847",
"Rebol": "#358a5b", "Rebol": "#358a5b",
"Record Jar": "#0673ba", "Record Jar": "#0673ba",
"Red": "#f50000", "Red": "#f50000",
@@ -377,6 +398,7 @@
"Roff": "#ecdebe", "Roff": "#ecdebe",
"Roff Manpage": "#ecdebe", "Roff Manpage": "#ecdebe",
"Rouge": "#cc0088", "Rouge": "#cc0088",
"RouterOS Script": "#DE3941",
"Ruby": "#701516", "Ruby": "#701516",
"Rust": "#dea584", "Rust": "#dea584",
"SAS": "#B34936", "SAS": "#B34936",
@@ -386,6 +408,7 @@
"SQL": "#e38c00", "SQL": "#e38c00",
"SQLPL": "#e38c00", "SQLPL": "#e38c00",
"SRecode Template": "#348a34", "SRecode Template": "#348a34",
"STL": "#373b5e",
"SVG": "#ff9900", "SVG": "#ff9900",
"SaltStack": "#646464", "SaltStack": "#646464",
"Sass": "#a53b70", "Sass": "#a53b70",
@@ -425,8 +448,9 @@
"TOML": "#9c4221", "TOML": "#9c4221",
"TSQL": "#e38c00", "TSQL": "#e38c00",
"TSV": "#237346", "TSV": "#237346",
"TSX": "#2b7489", "TSX": "#3178c6",
"TXL": "#0178b8", "TXL": "#0178b8",
"Talon": "#333333",
"Tcl": "#e4cc98", "Tcl": "#e4cc98",
"TeX": "#3D6117", "TeX": "#3D6117",
"Terra": "#00004c", "Terra": "#00004c",
@@ -435,7 +459,7 @@
"Thrift": "#D12127", "Thrift": "#D12127",
"Turing": "#cf142b", "Turing": "#cf142b",
"Twig": "#c1d026", "Twig": "#c1d026",
"TypeScript": "#2b7489", "TypeScript": "#3178c6",
"Unified Parallel C": "#4e3617", "Unified Parallel C": "#4e3617",
"Unity3D Asset": "#222c37", "Unity3D Asset": "#222c37",
"Uno": "#9933cc", "Uno": "#9933cc",
@@ -446,8 +470,9 @@
"VBScript": "#15dcdc", "VBScript": "#15dcdc",
"VCL": "#148AA8", "VCL": "#148AA8",
"VHDL": "#adb2cb", "VHDL": "#adb2cb",
"Vala": "#fbe5cd", "Vala": "#a56de2",
"Valve Data Format": "#f26025", "Valve Data Format": "#f26025",
"Velocity Template Language": "#507cff",
"Verilog": "#b2b7f8", "Verilog": "#b2b7f8",
"Vim Help File": "#199f4b", "Vim Help File": "#199f4b",
"Vim Script": "#199f4b", "Vim Script": "#199f4b",
@@ -458,11 +483,13 @@
"Vyper": "#2980b9", "Vyper": "#2980b9",
"Web Ontology Language": "#5b70bd", "Web Ontology Language": "#5b70bd",
"WebAssembly": "#04133b", "WebAssembly": "#04133b",
"Whiley": "#d5c397",
"Wikitext": "#fc5757", "Wikitext": "#fc5757",
"Windows Registry Entries": "#52d5ff", "Windows Registry Entries": "#52d5ff",
"Witcher Script": "#ff0000", "Witcher Script": "#ff0000",
"Wollok": "#a23738", "Wollok": "#a23738",
"World of Warcraft Addon Data": "#f7e43f", "World of Warcraft Addon Data": "#f7e43f",
"Wren": "#383838",
"X10": "#4B6BEF", "X10": "#4B6BEF",
"XC": "#99DA07", "XC": "#99DA07",
"XML": "#0060ac", "XML": "#0060ac",
@@ -476,6 +503,7 @@
"YARA": "#220000", "YARA": "#220000",
"YASnippet": "#32AB90", "YASnippet": "#32AB90",
"Yacc": "#4B6C4B", "Yacc": "#4B6C4B",
"Yul": "#794932",
"ZAP": "#0d665e", "ZAP": "#0d665e",
"ZIL": "#dc75e5", "ZIL": "#dc75e5",
"ZenScript": "#00BCD1", "ZenScript": "#00BCD1",
+3 -2
View File
@@ -1,4 +1,4 @@
const { logger, CustomError } = require("../common/utils"); import { CustomError, logger } from "./utils.js";
const retryer = async (fetcher, variables, retries = 0) => { const retryer = async (fetcher, variables, retries = 0) => {
if (retries > 7) { if (retries > 7) {
@@ -40,4 +40,5 @@ const retryer = async (fetcher, variables, retries = 0) => {
} }
}; };
module.exports = retryer; export { retryer };
export default retryer;
+5 -5
View File
@@ -1,8 +1,8 @@
// @ts-check // @ts-check
const axios = require("axios"); import axios from "axios";
const wrap = require("word-wrap"); import toEmoji from "emoji-name-map";
const themes = require("../../themes"); import wrap from "word-wrap";
const toEmoji = require("emoji-name-map"); import { themes } from "../../themes/index.js";
/** /**
* @param {string} message * @param {string} message
@@ -370,7 +370,7 @@ function parseEmojis(str) {
}); });
} }
module.exports = { export {
renderError, renderError,
kFormatter, kFormatter,
encodeHTML, encodeHTML,
+4 -3
View File
@@ -1,6 +1,6 @@
// @ts-check // @ts-check
const retryer = require("../common/retryer"); import { retryer } from "../common/retryer.js";
const { request, MissingParamError } = require("../common/utils"); import { MissingParamError, request } from "../common/utils.js";
/** /**
* @param {import('Axios').AxiosRequestHeaders} variables * @param {import('Axios').AxiosRequestHeaders} variables
@@ -97,4 +97,5 @@ async function fetchRepo(username, reponame) {
} }
} }
module.exports = fetchRepo; export { fetchRepo };
export default fetchRepo;
+31 -14
View File
@@ -1,17 +1,17 @@
// @ts-check // @ts-check
const axios = require("axios").default; import axios from "axios";
const githubUsernameRegex = require("github-username-regex"); import * as dotenv from "dotenv";
import githubUsernameRegex from "github-username-regex";
const retryer = require("../common/retryer"); import { calculateRank } from "../calculateRank.js";
const calculateRank = require("../calculateRank"); import { retryer } from "../common/retryer.js";
const { import {
request,
logger,
CustomError, CustomError,
logger,
MissingParamError, MissingParamError,
} = require("../common/utils"); request,
} from "../common/utils.js";
require("dotenv").config(); dotenv.config();
/** /**
* @param {import('axios').AxiosRequestHeaders} variables * @param {import('axios').AxiosRequestHeaders} variables
@@ -47,6 +47,7 @@ const fetcher = (variables, token) => {
repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) { repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}) {
totalCount totalCount
nodes { nodes {
name
stargazers { stargazers {
totalCount totalCount
} }
@@ -108,6 +109,7 @@ async function fetchStats(
username, username,
count_private = false, count_private = false,
include_all_commits = false, include_all_commits = false,
exclude_repo = [],
) { ) {
if (!username) throw new MissingParamError(["username"]); if (!username) throw new MissingParamError(["username"]);
@@ -133,6 +135,15 @@ async function fetchStats(
const user = res.data.data.user; const user = res.data.data.user;
// populate repoToHide map for quick lookup
// while filtering out
let repoToHide = {};
if (exclude_repo) {
exclude_repo.forEach((repoName) => {
repoToHide[repoName] = true;
});
}
stats.name = user.name || user.login; stats.name = user.name || user.login;
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount; stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
@@ -154,9 +165,14 @@ async function fetchStats(
stats.totalPRs = user.pullRequests.totalCount; stats.totalPRs = user.pullRequests.totalCount;
stats.contributedTo = user.repositoriesContributedTo.totalCount; stats.contributedTo = user.repositoriesContributedTo.totalCount;
stats.totalStars = user.repositories.nodes.reduce((prev, curr) => { // Retrieve stars while filtering out repositories to be hidden
return prev + curr.stargazers.totalCount; stats.totalStars = user.repositories.nodes
}, 0); .filter((data) => {
return !repoToHide[data.name];
})
.reduce((prev, curr) => {
return prev + curr.stargazers.totalCount;
}, 0);
stats.rank = calculateRank({ stats.rank = calculateRank({
totalCommits: stats.totalCommits, totalCommits: stats.totalCommits,
@@ -171,4 +187,5 @@ async function fetchStats(
return stats; return stats;
} }
module.exports = fetchStats; export { fetchStats };
export default fetchStats;
+7 -4
View File
@@ -1,7 +1,9 @@
// @ts-check // @ts-check
const { request, logger, MissingParamError } = require("../common/utils"); import * as dotenv from "dotenv";
const retryer = require("../common/retryer"); import { retryer } from "../common/retryer.js";
require("dotenv").config(); import { logger, MissingParamError, request } from "../common/utils.js";
dotenv.config();
/** /**
* @param {import('Axios').AxiosRequestHeaders} variables * @param {import('Axios').AxiosRequestHeaders} variables
@@ -104,4 +106,5 @@ async function fetchTopLanguages(username, exclude_repo = []) {
return topLangs; return topLangs;
} }
module.exports = fetchTopLanguages; export { fetchTopLanguages };
export default fetchTopLanguages;
+4 -5
View File
@@ -1,5 +1,5 @@
const axios = require("axios"); import axios from "axios";
const { MissingParamError } = require("../common/utils"); import { MissingParamError } from "../common/utils.js";
/** /**
* @param {{username: string, api_domain: string, range: string}} props * @param {{username: string, api_domain: string, range: string}} props
@@ -26,6 +26,5 @@ const fetchWakatimeStats = async ({ username, api_domain, range }) => {
} }
}; };
module.exports = { export { fetchWakatimeStats };
fetchWakatimeStats, export default fetchWakatimeStats;
};
+4 -3
View File
@@ -81,16 +81,17 @@ const getStyles = ({
animation: fadeInAnimation 0.3s ease-in-out forwards; animation: fadeInAnimation 0.3s ease-in-out forwards;
} }
.rank-text { .rank-text {
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor}; font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
animation: scaleInAnimation 0.3s ease-in-out forwards; animation: scaleInAnimation 0.3s ease-in-out forwards;
} }
.not_bold { font-weight: 400 }
.bold { font-weight: 700 } .bold { font-weight: 700 }
.icon { .icon {
fill: ${iconColor}; fill: ${iconColor};
display: ${!!show_icons ? "block" : "none"}; display: ${!!show_icons ? "block" : "none"};
} }
.rank-circle-rim { .rank-circle-rim {
stroke: ${titleColor}; stroke: ${titleColor};
fill: none; fill: none;
@@ -112,4 +113,4 @@ const getStyles = ({
`; `;
}; };
module.exports = { getStyles, getAnimations }; export { getStyles, getAnimations };
+2 -2
View File
@@ -1,4 +1,4 @@
const { encodeHTML } = require("./common/utils"); import { encodeHTML } from "./common/utils.js";
const statCardLocales = ({ name, apostrophe }) => { const statCardLocales = ({ name, apostrophe }) => {
const encodedName = encodeHTML(name); const encodedName = encodeHTML(name);
@@ -358,7 +358,7 @@ function isLocaleAvailable(locale) {
return availableLocales.includes(locale.toLowerCase()); return availableLocales.includes(locale.toLowerCase());
} }
module.exports = { export {
isLocaleAvailable, isLocaleAvailable,
availableLocales, availableLocales,
statCardLocales, statCardLocales,
@@ -1,179 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Test Render Wakatime Card should render correctly 1`] = ` exports[`Test Render Wakatime Card should render correctly 1`] = `[Function]`;
"
<svg
width=\\"495\\"
height=\\"150\\"
viewBox=\\"0 0 495 150\\"
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;
}
.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%\\">
<g transform=\\"translate(0, 0)\\">
<g class=\\"stagger\\" style=\\"animation-delay: NaNms\\" transform=\\"translate(25, 0)\\">
<text class=\\"stat bold\\" y=\\"12.5\\" data-testid=\\"Other\\">Other:</text>
<text
class=\\"stat\\"
x=\\"350\\"
y=\\"12.5\\"
>19 mins</text>
<svg width=\\"220\\" x=\\"110\\" y=\\"4\\">
<rect rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\" width=\\"220\\" height=\\"8\\" fill=\\"#434d58\\"></rect>
<rect
height=\\"8\\"
fill=\\"#2f80ed\\"
rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\"
data-testid=\\"lang-progress\\"
width=\\"2%\\"
>
</rect>
</svg>
</g>
</g><g transform=\\"translate(0, 25)\\">
<g class=\\"stagger\\" style=\\"animation-delay: NaNms\\" transform=\\"translate(25, 0)\\">
<text class=\\"stat bold\\" y=\\"12.5\\" data-testid=\\"TypeScript\\">TypeScript:</text>
<text
class=\\"stat\\"
x=\\"350\\"
y=\\"12.5\\"
>1 min</text>
<svg width=\\"220\\" x=\\"110\\" y=\\"4\\">
<rect rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\" width=\\"220\\" height=\\"8\\" fill=\\"#434d58\\"></rect>
<rect
height=\\"8\\"
fill=\\"#2f80ed\\"
rx=\\"5\\" ry=\\"5\\" x=\\"0\\" y=\\"0\\"
data-testid=\\"lang-progress\\"
width=\\"2%\\"
>
</rect>
</svg>
</g>
</g>
</svg>
</g>
</svg>
"
`;
exports[`Test Render Wakatime Card should render correctly with compact layout 1`] = ` exports[`Test Render Wakatime Card should render correctly with compact layout 1`] = `
" "
<svg <svg
width=\\"495\\" width="495"
height=\\"115\\" height="115"
viewBox=\\"0 0 495 115\\" viewBox="0 0 495 115"
fill=\\"none\\" fill="none"
xmlns=\\"http://www.w3.org/2000/svg\\" xmlns="http://www.w3.org/2000/svg"
role=\\"img\\" role="img"
aria-labelledby=\\"descId\\" aria-labelledby="descId"
> >
<title id=\\"titleId\\"></title> <title id="titleId"></title>
<desc id=\\"descId\\"></desc> <desc id="descId"></desc>
<style> <style>
.header { .header {
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
@@ -187,7 +28,7 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
.stat { .stat {
font: 600 14px 'Segoe UI', Ubuntu, \\"Helvetica Neue\\", Sans-Serif; fill: #434d58; font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: #434d58;
} }
@supports(-moz-appearance: auto) { @supports(-moz-appearance: auto) {
/* Selector detects Firefox */ /* Selector detects Firefox */
@@ -198,16 +39,17 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
animation: fadeInAnimation 0.3s ease-in-out forwards; animation: fadeInAnimation 0.3s ease-in-out forwards;
} }
.rank-text { .rank-text {
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58; font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58;
animation: scaleInAnimation 0.3s ease-in-out forwards; animation: scaleInAnimation 0.3s ease-in-out forwards;
} }
.not_bold { font-weight: 400 }
.bold { font-weight: 700 } .bold { font-weight: 700 }
.icon { .icon {
fill: #4c71f2; fill: #4c71f2;
display: none; display: none;
} }
.rank-circle-rim { .rank-circle-rim {
stroke: #2f80ed; stroke: #2f80ed;
fill: none; fill: none;
@@ -237,75 +79,75 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1
<rect <rect
data-testid=\\"card-bg\\" data-testid="card-bg"
x=\\"0.5\\" x="0.5"
y=\\"0.5\\" y="0.5"
rx=\\"4.5\\" rx="4.5"
height=\\"99%\\" height="99%"
stroke=\\"#e4e2e2\\" stroke="#e4e2e2"
width=\\"494\\" width="494"
fill=\\"#fffefe\\" fill="#fffefe"
stroke-opacity=\\"1\\" stroke-opacity="1"
/> />
<g <g
data-testid=\\"card-title\\" data-testid="card-title"
transform=\\"translate(25, 35)\\" transform="translate(25, 35)"
> >
<g transform=\\"translate(0, 0)\\"> <g transform="translate(0, 0)">
<text <text
x=\\"0\\" x="0"
y=\\"0\\" y="0"
class=\\"header\\" class="header"
data-testid=\\"header\\" data-testid="header"
>Wakatime Stats</text> >Wakatime Stats</text>
</g> </g>
</g> </g>
<g <g
data-testid=\\"main-card-body\\" data-testid="main-card-body"
transform=\\"translate(0, 55)\\" transform="translate(0, 55)"
> >
<svg x=\\"0\\" y=\\"0\\" width=\\"100%\\"> <svg x="0" y="0" width="100%">
<mask id=\\"rect-mask\\"> <mask id="rect-mask">
<rect x=\\"25\\" y=\\"0\\" width=\\"440\\" height=\\"8\\" fill=\\"white\\" rx=\\"5\\" /> <rect x="25" y="0" width="440" height="8" fill="white" rx="5" />
</mask> </mask>
<rect <rect
mask=\\"url(#rect-mask)\\" mask="url(#rect-mask)"
data-testid=\\"lang-progress\\" data-testid="lang-progress"
x=\\"0\\" x="0"
y=\\"0\\" y="0"
width=\\"6.6495\\" width="6.6495"
height=\\"8\\" height="8"
fill=\\"#858585\\" fill="#858585"
/> />
<rect <rect
mask=\\"url(#rect-mask)\\" mask="url(#rect-mask)"
data-testid=\\"lang-progress\\" data-testid="lang-progress"
x=\\"6.6495\\" x="6.6495"
y=\\"0\\" y="0"
width=\\"0.465\\" width="0.465"
height=\\"8\\" height="8"
fill=\\"#2b7489\\" fill="#3178c6"
/> />
<g transform=\\"translate(25, 25)\\"> <g transform="translate(25, 25)">
<circle cx=\\"5\\" cy=\\"6\\" r=\\"5\\" fill=\\"#858585\\" /> <circle cx="5" cy="6" r="5" fill="#858585" />
<text data-testid=\\"lang-name\\" x=\\"15\\" y=\\"10\\" class='lang-name'> <text data-testid="lang-name" x="15" y="10" class='lang-name'>
Other - 19 mins Other - 19 mins
</text> </text>
</g> </g>
<g transform=\\"translate(230, 25)\\"> <g transform="translate(230, 25)">
<circle cx=\\"5\\" cy=\\"6\\" r=\\"5\\" fill=\\"#2b7489\\" /> <circle cx="5" cy="6" r="5" fill="#3178c6" />
<text data-testid=\\"lang-name\\" x=\\"15\\" y=\\"10\\" class='lang-name'> <text data-testid="lang-name" x="15" y="10" class='lang-name'>
TypeScript - 1 min TypeScript - 1 min
</text> </text>
</g> </g>
+7 -7
View File
@@ -1,10 +1,10 @@
require("@testing-library/jest-dom"); import { jest } from "@jest/globals";
const axios = require("axios"); import axios from "axios";
const MockAdapter = require("axios-mock-adapter"); import MockAdapter from "axios-mock-adapter";
const api = require("../api/index"); import api from "../api/index.js";
const renderStatsCard = require("../src/cards/stats-card"); import { calculateRank } from "../src/calculateRank.js";
const { renderError, CONSTANTS } = require("../src/common/utils"); import { renderStatsCard } from "../src/cards/stats-card.js";
const calculateRank = require("../src/calculateRank"); import { CONSTANTS, renderError } from "../src/common/utils.js";
const stats = { const stats = {
name: "Anurag Hazra", name: "Anurag Hazra",
+2 -2
View File
@@ -1,5 +1,5 @@
require("@testing-library/jest-dom"); import "@testing-library/jest-dom";
const calculateRank = require("../src/calculateRank"); import { calculateRank } from "../src/calculateRank.js";
describe("Test calculateRank", () => { describe("Test calculateRank", () => {
it("should calculate rank correctly", () => { it("should calculate rank correctly", () => {
+6 -6
View File
@@ -1,9 +1,9 @@
require("@testing-library/jest-dom"); import { queryByTestId } from "@testing-library/dom";
const cssToObject = require("@uppercod/css-to-object").cssToObject; import "@testing-library/jest-dom";
const Card = require("../src/common/Card"); import { cssToObject } from "@uppercod/css-to-object";
const icons = require("../src/common/icons"); import { Card } from "../src/common/Card.js";
const { getCardColors } = require("../src/common/utils"); import { icons } from "../src/common/icons.js";
const { queryByTestId } = require("@testing-library/dom"); import { getCardColors } from "../src/common/utils.js";
describe("Card", () => { describe("Card", () => {
it("should hide border", () => { it("should hide border", () => {
+173
View File
@@ -0,0 +1,173 @@
/**
* @file Contains end-to-end tests for the Vercel preview instance.
*/
import dotenv from "dotenv";
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";
// Script variables
const REPO = "dummy-cra";
const USER = "grsdummy";
const STATS_DATA = {
name: "grsdummy",
totalPRs: 2,
totalCommits: 2,
totalIssues: 1,
totalStars: 1,
contributedTo: 2,
rank: {
level: "A+",
score: 51.01013099671447,
},
};
const LANGS_DATA = {
TypeScript: {
color: "#3178c6",
name: "TypeScript",
size: 2049,
},
HTML: {
color: "#e34c26",
name: "HTML",
size: 1721,
},
CSS: {
color: "#563d7c",
name: "CSS",
size: 930,
},
Python: {
color: "#3572A5",
name: "Python",
size: 671,
},
};
const WAKATIME_DATA = {
human_readable_range: "last week",
is_already_updating: false,
is_coding_activity_visible: false,
is_including_today: false,
is_other_usage_visible: false,
is_stuck: false,
is_up_to_date: false,
is_up_to_date_pending_future: false,
percent_calculated: 0,
range: "last_7_days",
status: "pending_update",
timeout: 15,
username: "grsdummy",
writes_only: false,
};
const REPOSITORY_DATA = {
name: "dummy-cra",
nameWithOwner: "grsdummy/dummy-cra",
isPrivate: false,
isArchived: false,
isTemplate: false,
stargazers: {
totalCount: 1,
},
description: "Dummy create react app.",
primaryLanguage: {
color: "#3178c6",
id: "MDg6TGFuZ3VhZ2UyODc=",
name: "TypeScript",
},
forkCount: 0,
starCount: 1,
};
describe("Fetch Cards", () => {
let VERCEL_PREVIEW_URL;
beforeAll(() => {
process.env.NODE_ENV = "development";
VERCEL_PREVIEW_URL = process.env.VERCEL_PREVIEW_URL;
});
test("retrieve stats card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
// Check if the Vercel preview instance stats card function is up and running.
await expect(
axios.get(`${VERCEL_PREVIEW_URL}/api?username=${USER}`),
).resolves.not.toThrow();
// Get local stats card.
const localStatsCardSVG = renderStatsCard(STATS_DATA);
// Get the Vercel preview stats card response.
const serverStatsSvg = await axios.get(
`${VERCEL_PREVIEW_URL}/api?username=${USER}`,
);
// Check if stats card from deployment matches the stats card from local.
expect(serverStatsSvg.data).toEqual(localStatsCardSVG);
});
test("retrieve language card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
// Check if the Vercel preview instance language card function is up and running.
await expect(
axios.get(`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}`),
).resolves.not.toThrow();
// Get local language card.
const localLanguageCardSVG = renderTopLanguages(LANGS_DATA);
// Get the Vercel preview language card response.
const severLanguageSVG = await axios.get(
`${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}`,
);
// Check if language card from deployment matches the local language card.
expect(severLanguageSVG.data).toEqual(localLanguageCardSVG);
});
test("retrieve WakaTime card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
// Check if the Vercel preview instance WakaTime function is up and running.
await expect(
axios.get(`${VERCEL_PREVIEW_URL}/api/wakatime?username=${USER}`),
).resolves.not.toThrow();
// Get local WakaTime card.
const localWakaCardSVG = renderWakatimeCard(WAKATIME_DATA);
// Get the Vercel preview WakaTime card response.
const serverWakaTimeSvg = await axios.get(
`${VERCEL_PREVIEW_URL}/api/wakatime?username=${USER}`,
);
// Check if WakaTime card from deployment matches the local WakaTime card.
expect(serverWakaTimeSvg.data).toEqual(localWakaCardSVG);
});
test("retrieve repo card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
// Check if the Vercel preview instance Repo function is up and running.
await expect(
axios.get(`${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}`),
).resolves.not.toThrow();
// Get local repo card.
const localRepoCardSVG = renderRepoCard(REPOSITORY_DATA);
// Get the Vercel preview repo card response.
const serverRepoSvg = await axios.get(
`${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}`,
);
// Check if Repo card from deployment matches the local Repo card.
expect(serverRepoSvg.data).toEqual(localRepoCardSVG);
});
});
+4 -4
View File
@@ -1,7 +1,7 @@
require("@testing-library/jest-dom"); import "@testing-library/jest-dom";
const axios = require("axios"); import axios from "axios";
const MockAdapter = require("axios-mock-adapter"); import MockAdapter from "axios-mock-adapter";
const fetchRepo = require("../src/fetchers/repo-fetcher"); import { fetchRepo } from "../src/fetchers/repo-fetcher.js";
const data_repo = { const data_repo = {
repository: { repository: {
+38 -10
View File
@@ -1,8 +1,8 @@
require("@testing-library/jest-dom"); import "@testing-library/jest-dom";
const axios = require("axios"); import axios from "axios";
const MockAdapter = require("axios-mock-adapter"); import MockAdapter from "axios-mock-adapter";
const fetchStats = require("../src/fetchers/stats-fetcher"); import { calculateRank } from "../src/calculateRank.js";
const calculateRank = require("../src/calculateRank"); import { fetchStats } from "../src/fetchers/stats-fetcher.js";
const data = { const data = {
data: { data: {
@@ -20,11 +20,11 @@ const data = {
repositories: { repositories: {
totalCount: 5, totalCount: 5,
nodes: [ nodes: [
{ stargazers: { totalCount: 100 } }, { name: "test-repo-1", stargazers: { totalCount: 100 } },
{ stargazers: { totalCount: 100 } }, { name: "test-repo-2", stargazers: { totalCount: 100 } },
{ stargazers: { totalCount: 100 } }, { name: "test-repo-3", stargazers: { totalCount: 100 } },
{ stargazers: { totalCount: 50 } }, { name: "test-repo-4", stargazers: { totalCount: 50 } },
{ stargazers: { totalCount: 50 } }, { name: "test-repo-5", stargazers: { totalCount: 50 } },
], ],
}, },
}, },
@@ -134,4 +134,32 @@ describe("Test fetchStats", () => {
rank, rank,
}); });
}); });
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 });
let stats = await fetchStats("anuraghazra", true, true, ["test-repo-1"]);
const rank = calculateRank({
totalCommits: 1050,
totalRepos: 5,
followers: 100,
contributions: 61,
stargazers: 300,
prs: 300,
issues: 200,
});
expect(stats).toStrictEqual({
contributedTo: 61,
name: "Anurag Hazra",
totalCommits: 1050,
totalIssues: 200,
totalPRs: 300,
totalStars: 300,
rank,
});
});
}); });
+5 -8
View File
@@ -1,7 +1,7 @@
require("@testing-library/jest-dom"); import "@testing-library/jest-dom";
const axios = require("axios"); import axios from "axios";
const MockAdapter = require("axios-mock-adapter"); import MockAdapter from "axios-mock-adapter";
const fetchTopLanguages = require("../src/fetchers/top-languages-fetcher"); import { fetchTopLanguages } from "../src/fetchers/top-languages-fetcher.js";
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
@@ -81,10 +81,7 @@ describe("FetchTopLanguages", () => {
it("should fetch correct language data while excluding the 'test-repo-1' repository", async () => { it("should fetch correct language data while excluding the 'test-repo-1' repository", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_langs); mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
let repo = await fetchTopLanguages( let repo = await fetchTopLanguages("anuraghazra", ["test-repo-1"]);
"anuraghazra",
(exclude_repo = ["test-repo-1"]),
);
expect(repo).toStrictEqual({ expect(repo).toStrictEqual({
HTML: { HTML: {
color: "#0f0", color: "#0f0",
+16 -16
View File
@@ -1,7 +1,7 @@
require("@testing-library/jest-dom"); import "@testing-library/jest-dom";
const axios = require("axios"); import axios from "axios";
const MockAdapter = require("axios-mock-adapter"); import MockAdapter from "axios-mock-adapter";
const { fetchWakatimeStats } = require("../src/fetchers/wakatime-fetcher"); import { fetchWakatimeStats } from "../src/fetchers/wakatime-fetcher.js";
const mock = new MockAdapter(axios); const mock = new MockAdapter(axios);
afterEach(() => { afterEach(() => {
@@ -111,9 +111,9 @@ describe("Wakatime fetcher", () => {
const repo = await fetchWakatimeStats({ username }); const repo = await fetchWakatimeStats({ username });
expect(repo).toMatchInlineSnapshot(` expect(repo).toMatchInlineSnapshot(`
Object { {
"categories": Array [ "categories": [
Object { {
"digital": "22:40", "digital": "22:40",
"hours": 22, "hours": 22,
"minutes": 40, "minutes": 40,
@@ -127,8 +127,8 @@ describe("Wakatime fetcher", () => {
"daily_average_including_other_language": 16329, "daily_average_including_other_language": 16329,
"days_including_holidays": 7, "days_including_holidays": 7,
"days_minus_holidays": 5, "days_minus_holidays": 5,
"editors": Array [ "editors": [
Object { {
"digital": "22:40", "digital": "22:40",
"hours": 22, "hours": 22,
"minutes": 40, "minutes": 40,
@@ -150,8 +150,8 @@ describe("Wakatime fetcher", () => {
"is_other_usage_visible": true, "is_other_usage_visible": true,
"is_stuck": false, "is_stuck": false,
"is_up_to_date": true, "is_up_to_date": true,
"languages": Array [ "languages": [
Object { {
"digital": "0:19", "digital": "0:19",
"hours": 0, "hours": 0,
"minutes": 19, "minutes": 19,
@@ -160,7 +160,7 @@ describe("Wakatime fetcher", () => {
"text": "19 mins", "text": "19 mins",
"total_seconds": 1170.434361, "total_seconds": 1170.434361,
}, },
Object { {
"digital": "0:01", "digital": "0:01",
"hours": 0, "hours": 0,
"minutes": 1, "minutes": 1,
@@ -169,7 +169,7 @@ describe("Wakatime fetcher", () => {
"text": "1 min", "text": "1 min",
"total_seconds": 83.293809, "total_seconds": 83.293809,
}, },
Object { {
"digital": "0:00", "digital": "0:00",
"hours": 0, "hours": 0,
"minutes": 0, "minutes": 0,
@@ -179,8 +179,8 @@ describe("Wakatime fetcher", () => {
"total_seconds": 54.975151, "total_seconds": 54.975151,
}, },
], ],
"operating_systems": Array [ "operating_systems": [
Object { {
"digital": "22:40", "digital": "22:40",
"hours": 22, "hours": 22,
"minutes": 40, "minutes": 40,
@@ -212,4 +212,4 @@ describe("Wakatime fetcher", () => {
}); });
}); });
module.exports = { wakaTimeData }; export { wakaTimeData };
+1 -1
View File
@@ -1,4 +1,4 @@
const { flexLayout } = require("../src/common/utils"); import { flexLayout } from "../src/common/utils.js";
describe("flexLayout", () => { describe("flexLayout", () => {
it("should work with row & col layouts", () => { it("should work with row & col layouts", () => {
+7 -6
View File
@@ -1,9 +1,10 @@
require("@testing-library/jest-dom"); import { jest } from "@jest/globals";
const axios = require("axios"); import "@testing-library/jest-dom";
const MockAdapter = require("axios-mock-adapter"); import axios from "axios";
const pin = require("../api/pin"); import MockAdapter from "axios-mock-adapter";
const renderRepoCard = require("../src/cards/repo-card"); import pin from "../api/pin.js";
const { renderError } = require("../src/common/utils"); import { renderRepoCard } from "../src/cards/repo-card.js";
import { renderError } from "../src/common/utils.js";
const data_repo = { const data_repo = {
repository: { repository: {
+5 -5
View File
@@ -1,9 +1,9 @@
require("@testing-library/jest-dom"); import { queryByTestId } from "@testing-library/dom";
const cssToObject = require("@uppercod/css-to-object").cssToObject; import "@testing-library/jest-dom";
const renderRepoCard = require("../src/cards/repo-card"); import { cssToObject } from "@uppercod/css-to-object";
import { renderRepoCard } from "../src/cards/repo-card.js";
const { queryByTestId } = require("@testing-library/dom"); import { themes } from "../themes/index.js";
const themes = require("../themes");
const data_repo = { const data_repo = {
repository: { repository: {
+9 -8
View File
@@ -1,13 +1,14 @@
require("@testing-library/jest-dom"); import {
const cssToObject = require("@uppercod/css-to-object").cssToObject;
const renderStatsCard = require("../src/cards/stats-card");
const {
getByTestId, getByTestId,
queryByTestId,
queryAllByTestId, queryAllByTestId,
} = require("@testing-library/dom"); queryByTestId,
const themes = require("../themes"); } from "@testing-library/dom";
import { cssToObject } from "@uppercod/css-to-object";
import { renderStatsCard } from "../src/cards/stats-card.js";
// adds special assertions like toHaveTextContent
import "@testing-library/jest-dom";
import { themes } from "../themes/index.js";
describe("Test renderStatsCard", () => { describe("Test renderStatsCard", () => {
const stats = { const stats = {
+13 -9
View File
@@ -1,9 +1,13 @@
require("@testing-library/jest-dom"); import { queryAllByTestId, queryByTestId } from "@testing-library/dom";
const cssToObject = require("@uppercod/css-to-object").cssToObject; import { cssToObject } from "@uppercod/css-to-object";
const renderTopLanguages = require("../src/cards/top-languages-card"); import {
MIN_CARD_WIDTH,
renderTopLanguages,
} from "../src/cards/top-languages-card.js";
// adds special assertions like toHaveTextContent
import "@testing-library/jest-dom";
const { queryByTestId, queryAllByTestId } = require("@testing-library/dom"); import { themes } from "../themes/index.js";
const themes = require("../themes");
describe("Test renderTopLanguages", () => { describe("Test renderTopLanguages", () => {
const langs = { const langs = {
@@ -108,13 +112,13 @@ describe("Test renderTopLanguages", () => {
expect(document.querySelector("svg")).toHaveAttribute( expect(document.querySelector("svg")).toHaveAttribute(
"width", "width",
renderTopLanguages.MIN_CARD_WIDTH.toString(), MIN_CARD_WIDTH.toString(),
); );
document.body.innerHTML = renderTopLanguages(langs, { card_width: 100 }); document.body.innerHTML = renderTopLanguages(langs, { card_width: 100 });
expect(document.querySelector("svg")).toHaveAttribute( expect(document.querySelector("svg")).toHaveAttribute(
"width", "width",
renderTopLanguages.MIN_CARD_WIDTH.toString(), MIN_CARD_WIDTH.toString(),
); );
}); });
@@ -247,7 +251,7 @@ describe("Test renderTopLanguages", () => {
}); });
it("should render langs with specified langs_count", async () => { it("should render langs with specified langs_count", async () => {
options = { const options = {
langs_count: 1, langs_count: 1,
}; };
document.body.innerHTML = renderTopLanguages(langs, { ...options }); document.body.innerHTML = renderTopLanguages(langs, { ...options });
@@ -257,7 +261,7 @@ describe("Test renderTopLanguages", () => {
}); });
it("should render langs with specified langs_count even when hide is set", async () => { it("should render langs with specified langs_count even when hide is set", async () => {
options = { const options = {
hide: ["HTML"], hide: ["HTML"],
langs_count: 2, langs_count: 2,
}; };
+6 -7
View File
@@ -1,14 +1,13 @@
require("@testing-library/jest-dom"); import { queryByTestId } from "@testing-library/dom";
const { queryByTestId } = require("@testing-library/dom"); import "@testing-library/jest-dom";
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
const renderWakatimeCard = require("../src/cards/wakatime-card"); import { getCardColors } from "../src/common/utils.js";
const { wakaTimeData } = require("./fetchWakatime.test"); import { wakaTimeData } from "./fetchWakatime.test.js";
describe("Test Render Wakatime Card", () => { describe("Test Render Wakatime Card", () => {
it("should render correctly", () => { it("should render correctly", () => {
const card = renderWakatimeCard(wakaTimeData.data); const card = renderWakatimeCard(wakaTimeData.data);
expect(getCardColors).toMatchSnapshot();
expect(card).toMatchSnapshot();
}); });
it("should render correctly with compact layout", () => { it("should render correctly with compact layout", () => {
+4 -3
View File
@@ -1,6 +1,7 @@
require("@testing-library/jest-dom"); import { jest } from "@jest/globals";
const retryer = require("../src/common/retryer"); import "@testing-library/jest-dom";
const { logger } = require("../src/common/utils"); import { retryer } from "../src/common/retryer.js";
import { logger } from "../src/common/utils.js";
const fetcher = jest.fn((variables, token) => { const fetcher = jest.fn((variables, token) => {
logger.log(variables, token); logger.log(variables, token);
+7 -6
View File
@@ -1,9 +1,10 @@
require("@testing-library/jest-dom"); import { jest } from "@jest/globals";
const axios = require("axios"); import "@testing-library/jest-dom";
const MockAdapter = require("axios-mock-adapter"); import axios from "axios";
const topLangs = require("../api/top-langs"); import MockAdapter from "axios-mock-adapter";
const renderTopLanguages = require("../src/cards/top-languages-card"); import topLangs from "../api/top-langs.js";
const { renderError } = require("../src/common/utils"); import { renderTopLanguages } from "../src/cards/top-languages-card.js";
import { renderError } from "../src/common/utils.js";
const data_langs = { const data_langs = {
data: { data: {
+9 -9
View File
@@ -1,14 +1,12 @@
require("@testing-library/jest-dom"); import { queryByTestId } from "@testing-library/dom";
const { import "@testing-library/jest-dom";
kFormatter, import {
encodeHTML, encodeHTML,
renderError,
flexLayout,
getCardColors, getCardColors,
kFormatter,
renderError,
wrapTextMultiline, wrapTextMultiline,
} = require("../src/common/utils"); } from "../src/common/utils.js";
const { queryByTestId } = require("@testing-library/dom");
describe("Test utils.js", () => { describe("Test utils.js", () => {
it("should test kFormatter", () => { it("should test kFormatter", () => {
@@ -32,7 +30,9 @@ describe("Test utils.js", () => {
expect( expect(
queryByTestId(document.body, "message").children[0], queryByTestId(document.body, "message").children[0],
).toHaveTextContent(/Something went wrong/gim); ).toHaveTextContent(/Something went wrong/gim);
expect(queryByTestId(document.body, "message").children[1]).toBeEmpty(2); expect(
queryByTestId(document.body, "message").children[1],
).toBeEmptyDOMElement(2);
// Secondary message // Secondary message
document.body.innerHTML = renderError( document.body.innerHTML = renderError(
+44 -40
View File
@@ -16,26 +16,27 @@ Use `?theme=THEME_NAME` parameter like so :-
| | | | | | | |
| :--: | :--: | :--: | | :--: | :--: | :--: |
| `default` ![default][default] | `dark` ![dark][dark] | `radical` ![radical][radical] | | `default` ![default][default] | `transparent` ![transparent][transparent] | `dark` ![dark][dark] |
| `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | `gruvbox_light` ![gruvbox_light][gruvbox_light] | | `radical` ![radical][radical] | `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] |
| `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] | `cobalt` ![cobalt][cobalt] | | `gruvbox_light` ![gruvbox_light][gruvbox_light] | `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] |
| `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] | `dracula` ![dracula][dracula] | | `cobalt` ![cobalt][cobalt] | `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] |
| `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] | `vue` ![vue][vue] | | `dracula` ![dracula][dracula] | `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] |
| `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] | `nightowl` ![nightowl][nightowl] | | `vue` ![vue][vue] | `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] |
| `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] | `algolia` ![algolia][algolia] | | `nightowl` ![nightowl][nightowl] | `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] |
| `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] | `bear` ![bear][bear] | | `algolia` ![algolia][algolia] | `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] |
| `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | | `bear` ![bear][bear] | `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] |
| `nord` ![nord][nord] | `gotham` ![gotham][gotham] | `material-palenight` ![material-palenight][material-palenight] | | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | `nord` ![nord][nord] | `gotham` ![gotham][gotham] |
| `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | `ayu-mirage` ![ayu-mirage][ayu-mirage] | | `material-palenight` ![material-palenight][material-palenight] | `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] |
| `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] | `flag-india` ![flag-india][flag-india] | | `ayu-mirage` ![ayu-mirage][ayu-mirage] | `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] |
| `omni` ![omni][omni] | `react` ![react][react] | `jolly` ![jolly][jolly] | | `flag-india` ![flag-india][flag-india] | `omni` ![omni][omni] | `react` ![react][react] |
| `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | `blueberry` ![blueberry][blueberry] | | `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] |
| `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | `outrun` ![outrun][outrun] | | `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] |
| `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | `github_dark` ![github_dark][github_dark] | | `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] |
| `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] | `panda` ![panda][panda] | | `github_dark` ![github_dark][github_dark] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] |
| `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] | `swift` ![swift][swift] | | `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] |
| `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] | `moltack` ![moltack][moltack] | | `swift` ![swift][swift] | `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] |
| `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] | [Add your theme][add-theme] | | `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] |
| [Add your theme][add-theme] | | |
## Repo Card ## Repo Card
@@ -43,30 +44,32 @@ Use `?theme=THEME_NAME` parameter like so :-
| | | | | | | |
| :--: | :--: | :--: | | :--: | :--: | :--: |
| `default_repocard` ![default_repocard][default_repocard_repo] | `dark` ![dark][dark_repo] | `radical` ![radical][radical_repo] | | `default_repocard` ![default_repocard][default_repocard_repo] | `transparent` ![transparent][transparent_repo] | `dark` ![dark][dark_repo] |
| `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] | | `radical` ![radical][radical_repo] | `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] |
| `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] | `cobalt` ![cobalt][cobalt_repo] | | `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] | `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] |
| `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] | `dracula` ![dracula][dracula_repo] | | `cobalt` ![cobalt][cobalt_repo] | `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] |
| `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] | `vue` ![vue][vue_repo] | | `dracula` ![dracula][dracula_repo] | `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] |
| `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | `nightowl` ![nightowl][nightowl_repo] | | `vue` ![vue][vue_repo] | `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] |
| `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] | `algolia` ![algolia][algolia_repo] | | `nightowl` ![nightowl][nightowl_repo] | `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] |
| `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] | `bear` ![bear][bear_repo] | | `algolia` ![algolia][algolia_repo] | `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] |
| `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | | `bear` ![bear][bear_repo] | `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] |
| `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] | `material-palenight` ![material-palenight][material-palenight_repo] | | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] |
| `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | | `material-palenight` ![material-palenight][material-palenight_repo] | `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] |
| `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] | `flag-india` ![flag-india][flag-india_repo] | | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] |
| `omni` ![omni][omni_repo] | `react` ![react][react_repo] | `jolly` ![jolly][jolly_repo] | | `flag-india` ![flag-india][flag-india_repo] | `omni` ![omni][omni_repo] | `react` ![react][react_repo] |
| `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | `blueberry` ![blueberry][blueberry_repo] | | `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] |
| `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | `outrun` ![outrun][outrun_repo] | | `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] |
| `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | `github_dark` ![github_dark][github_dark_repo] | | `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] |
| `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] | `panda` ![panda][panda_repo] | | `github_dark` ![github_dark][github_dark_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] |
| `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] | `swift` ![swift][swift_repo] | | `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] |
| `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] | `moltack` ![moltack][moltack_repo] | | `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] |
| `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] | [Add your theme][add-theme] | | `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] |
| [Add your theme][add-theme] | | |
[default]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default [default]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default
[default_repocard]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default_repocard [default_repocard]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default_repocard
[transparent]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=transparent
[dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=dark [dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=dark
[radical]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=radical [radical]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=radical
[merko]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=merko [merko]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=merko
@@ -129,6 +132,7 @@ Use `?theme=THEME_NAME` parameter like so :-
[default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default [default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default
[default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard [default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard
[transparent_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=transparent
[dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=dark [dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=dark
[radical_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=radical [radical_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=radical
[merko_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=merko [merko_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=merko
+8 -2
View File
@@ -1,4 +1,4 @@
const themes = { export const themes = {
default: { default: {
title_color: "2f80ed", title_color: "2f80ed",
icon_color: "4c71f2", icon_color: "4c71f2",
@@ -12,6 +12,12 @@ const themes = {
text_color: "434d58", text_color: "434d58",
bg_color: "fffefe", bg_color: "fffefe",
}, },
transparent: {
title_color: "006AFF",
icon_color: "0579C3",
text_color: "417E87",
bg_color: "ffffff00",
},
dark: { dark: {
title_color: "fff", title_color: "fff",
icon_color: "79ff97", icon_color: "79ff97",
@@ -363,4 +369,4 @@ const themes = {
}, },
}; };
module.exports = themes; export default themes;