Skip to content

Commit 85526f3

Browse files
Merge pull request anuraghazra#1 from meyer-pidiache/base
Pull from fork
2 parents 7e695e4 + 29c44eb commit 85526f3

40 files changed

+1521
-333
lines changed

.github/labeler.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
themes: themes/index.js
22
doc-translation: docs/*
33
card-i18n: src/translations.js
4+
documentation: readme.md

.github/workflows/deploy-prep.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
3+
file = open('./vercel.json', 'r')
4+
str = file.read()
5+
file = open('./vercel.json', 'w')
6+
7+
str = str.replace('"maxDuration": 10', '"maxDuration": 30')
8+
9+
file.write(str)
10+
file.close()

.github/workflows/deploy-prep.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Deployment Prep
2+
on:
3+
workflow_dispatch:
4+
push:
5+
branches:
6+
- master
7+
8+
jobs:
9+
config:
10+
if: github.repository == 'anuraghazra/github-readme-stats'
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- name: Deployment Prep
15+
run: python ./.github/workflows/deploy-prep.py
16+
- uses: stefanzweifel/git-auto-commit-action@v4
17+
with:
18+
branch: vercel
19+
create_branch: true
20+
push_options: "--force"

.github/workflows/e2e-test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
jobs:
66
e2eTests:
77
if:
8+
github.repository == 'meyer-pidiache/github-readme-stats' &&
89
github.event_name == 'deployment_status' &&
910
github.event.deployment_status.state == 'success'
1011
name: Perform 2e2 tests

.github/workflows/empty-issues-closer.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88

99
jobs:
1010
closeEmptyIssuesAndTemplates:
11+
if: github.repository == 'anuraghazra/github-readme-stats'
1112
name: Close empty issues
1213
runs-on: ubuntu-latest
1314
steps:

.github/workflows/generate-theme-doc.yml

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ jobs:
2323
node-version: ${{ matrix.node-version }}
2424
cache: npm
2525

26+
# Fix the unsafe repo error which was introduced by the CVE-2022-24765 git patches.
27+
- name: Fix unsafe repo error
28+
run: git config --global --add safe.directory ${{ github.workspace }}
29+
2630
- name: npm install, generate readme
2731
run: |
2832
npm ci

.github/workflows/label-pr.yml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44

55
jobs:
66
triage:
7+
if: github.repository == 'meyer-pidiache/github-readme-stats'
78
runs-on: ubuntu-latest
89
steps:
910
- uses: actions/labeler@v4

.github/workflows/stale-theme-pr-closer.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55

66
jobs:
77
closeOldThemePrs:
8+
if: github.repository == 'meyer-pidiache/github-readme-stats'
89
name: Close stale 'invalid' theme PRs
910
runs-on: ubuntu-latest
1011
strategy:

.github/workflows/top-issues-dashboard.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55

66
jobs:
77
showAndLabelTopIssues:
8+
if: github.repository == 'anuraghazra/github-readme-stats'
89
name: Update top issues Dashboard.
910
runs-on: ubuntu-latest
1011
steps:

.github/workflows/update-langs.yaml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Update supported languages
2+
on:
3+
schedule:
4+
- cron: "0 0 */30 * *"
5+
6+
jobs:
7+
updateLanguages:
8+
if: github.repository == 'anuraghazra/github-readme-stats'
9+
name: Update supported languages
10+
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
node-version: [16.x]
14+
15+
steps:
16+
- uses: actions/checkout@v3
17+
18+
- name: Setup Node
19+
uses: actions/setup-node@v3
20+
with:
21+
node-version: ${{ matrix.node-version }}
22+
cache: npm
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
env:
27+
CI: true
28+
29+
- name: Run update-languages-json.js script
30+
run: npm run generate-langs-json
31+
32+
- name: Create Pull Request if upstream language file is changed
33+
uses: peter-evans/create-pull-request@v4
34+
with:
35+
commit-message: "refactor: update languages JSON"
36+
branch: "update_langs/patch"
37+
delete-branch: true
38+
title: Update languages JSON
39+
body:
40+
"The
41+
[update-langs](https://github.com/anuraghazra/github-readme-stats/actions/workflows/update-langs.yaml)
42+
action found new/updated languages in the [upstream languages JSON
43+
file](https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml)."
44+
labels: "ci, lang-card"

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ vercel_token
1010
# IDE
1111
.vscode
1212
*.code-workspace
13+
14+
.vercel

api/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export default async (req, res) => {
3535
locale,
3636
disable_animations,
3737
border_radius,
38+
number_format,
3839
border_color,
3940
} = req.query;
4041
res.setHeader("Content-Type", "image/svg+xml");
@@ -88,6 +89,7 @@ export default async (req, res) => {
8889
custom_title,
8990
border_radius,
9091
border_color,
92+
number_format,
9193
locale: locale ? locale.toLowerCase() : null,
9294
disable_animations: parseBoolean(disable_animations),
9395
}),

api/status/pat-info.js

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* @file Contains a simple cloud function that can be used to check which PATs are no
3+
* longer working. It returns a list of valid PATs, expired PATs and PATs with errors.
4+
*
5+
* @description This function is currently rate limited to 1 request per 5 minutes.
6+
*/
7+
8+
import { logger, request, dateDiff } from "../../src/common/utils.js";
9+
export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes
10+
11+
/**
12+
* Simple uptime check fetcher for the PATs.
13+
*
14+
* @param {import('axios').AxiosRequestHeaders} variables
15+
* @param {string} token
16+
*/
17+
const uptimeFetcher = (variables, token) => {
18+
return request(
19+
{
20+
query: `
21+
query {
22+
rateLimit {
23+
remaining
24+
resetAt
25+
},
26+
}`,
27+
variables,
28+
},
29+
{
30+
Authorization: `bearer ${token}`,
31+
},
32+
);
33+
};
34+
35+
const getAllPATs = () => {
36+
return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key));
37+
};
38+
39+
/**
40+
* Check whether any of the PATs is expired.
41+
*/
42+
const getPATInfo = async (fetcher, variables) => {
43+
const details = {};
44+
const PATs = getAllPATs();
45+
46+
for (const pat of PATs) {
47+
try {
48+
const response = await fetcher(variables, process.env[pat]);
49+
const errors = response.data.errors;
50+
const hasErrors = Boolean(errors);
51+
const errorType = errors?.[0]?.type;
52+
const isRateLimited =
53+
(hasErrors && errorType === "RATE_LIMITED") ||
54+
response.data.data?.rateLimit?.remaining === 0;
55+
56+
// Store PATs with errors.
57+
if (hasErrors && errorType !== "RATE_LIMITED") {
58+
details[pat] = {
59+
status: "error",
60+
error: {
61+
type: errors[0].type,
62+
message: errors[0].message,
63+
},
64+
};
65+
continue;
66+
} else if (isRateLimited) {
67+
const date1 = new Date();
68+
const date2 = new Date(response.data?.data?.rateLimit?.resetAt);
69+
details[pat] = {
70+
status: "exhausted",
71+
remaining: 0,
72+
resetIn: dateDiff(date2, date1) + " minutes",
73+
};
74+
} else {
75+
details[pat] = {
76+
status: "valid",
77+
remaining: response.data.data.rateLimit.remaining,
78+
};
79+
}
80+
} catch (err) {
81+
// Store the PAT if it is expired.
82+
const errorMessage = err.response?.data?.message?.toLowerCase();
83+
if (errorMessage === "bad credentials") {
84+
details[pat] = {
85+
status: "expired",
86+
};
87+
} else if (errorMessage === "sorry. your account was suspended.") {
88+
details[pat] = {
89+
status: "suspended",
90+
};
91+
} else {
92+
throw err;
93+
}
94+
}
95+
}
96+
97+
const filterPATsByStatus = (status) => {
98+
return Object.keys(details).filter((pat) => details[pat].status === status);
99+
};
100+
101+
const sortedDetails = Object.keys(details)
102+
.sort()
103+
.reduce((obj, key) => {
104+
obj[key] = details[key];
105+
return obj;
106+
}, {});
107+
108+
return {
109+
validPATs: filterPATsByStatus("valid"),
110+
expiredPATs: filterPATsByStatus("expired"),
111+
exhaustedPATs: filterPATsByStatus("exhausted"),
112+
suspendedPATs: filterPATsByStatus("suspended"),
113+
errorPATs: filterPATsByStatus("error"),
114+
details: sortedDetails,
115+
};
116+
};
117+
118+
/**
119+
* Cloud function that returns information about the used PATs.
120+
*/
121+
export default async (_, res) => {
122+
res.setHeader("Content-Type", "application/json");
123+
try {
124+
// Add header to prevent abuse.
125+
const PATsInfo = await getPATInfo(uptimeFetcher, {});
126+
if (PATsInfo) {
127+
res.setHeader(
128+
"Cache-Control",
129+
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
130+
);
131+
}
132+
res.send(JSON.stringify(PATsInfo, null, 2));
133+
} catch (err) {
134+
// Throw error if something went wrong.
135+
logger.error(err);
136+
res.setHeader("Cache-Control", "no-store");
137+
res.send("Something went wrong: " + err.message);
138+
}
139+
};

api/status/up.js

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* @file Contains a simple cloud function that can be used to check if the PATs are still
3+
* functional.
4+
*
5+
* @description This function is currently rate limited to 1 request per 5 minutes.
6+
*/
7+
8+
import retryer from "../../src/common/retryer.js";
9+
import { logger, request } from "../../src/common/utils.js";
10+
11+
export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes
12+
13+
/**
14+
* Simple uptime check fetcher for the PATs.
15+
*
16+
* @param {import('axios').AxiosRequestHeaders} variables
17+
* @param {string} token
18+
*/
19+
const uptimeFetcher = (variables, token) => {
20+
return request(
21+
{
22+
query: `
23+
query {
24+
rateLimit {
25+
remaining
26+
}
27+
}
28+
`,
29+
variables,
30+
},
31+
{
32+
Authorization: `bearer ${token}`,
33+
},
34+
);
35+
};
36+
37+
/**
38+
* Creates Json response that can be used for shields.io dynamic card generation.
39+
*
40+
* @param {*} up Whether the PATs are up or not.
41+
* @returns Dynamic shields.io JSON response object.
42+
*
43+
* @see https://shields.io/endpoint.
44+
*/
45+
const shieldsUptimeBadge = (up) => {
46+
const schemaVersion = 1;
47+
const isError = true;
48+
const label = "Public Instance";
49+
const message = up ? "up" : "down";
50+
const color = up ? "brightgreen" : "red";
51+
return {
52+
schemaVersion,
53+
label,
54+
message,
55+
color,
56+
isError,
57+
};
58+
};
59+
60+
/**
61+
* Cloud function that returns whether the PATs are still functional.
62+
*/
63+
export default async (req, res) => {
64+
let { type } = req.query;
65+
type = type ? type.toLowerCase() : "boolean";
66+
67+
res.setHeader("Content-Type", "application/json");
68+
69+
try {
70+
let PATsValid = true;
71+
try {
72+
await retryer(uptimeFetcher, {});
73+
} catch (err) {
74+
PATsValid = false;
75+
}
76+
77+
if (PATsValid) {
78+
res.setHeader(
79+
"Cache-Control",
80+
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
81+
);
82+
} else {
83+
res.setHeader("Cache-Control", "no-store");
84+
}
85+
86+
switch (type) {
87+
case "shields":
88+
res.send(shieldsUptimeBadge(PATsValid));
89+
break;
90+
case "json":
91+
res.send({ up: PATsValid });
92+
break;
93+
default:
94+
res.send(PATsValid);
95+
break;
96+
}
97+
} catch (err) {
98+
// Return fail boolean if something went wrong.
99+
logger.error(err);
100+
res.setHeader("Cache-Control", "no-store");
101+
res.send("Something went wrong: " + err.message);
102+
}
103+
};

0 commit comments

Comments
 (0)