@stats-organization/github-readme-stats-core 2.1.4 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/build/api/gist.d.ts.map +1 -1
  2. package/build/api/gist.js +34 -0
  3. package/build/api/index.d.ts.map +1 -1
  4. package/build/api/index.js +18 -0
  5. package/build/api/pin.d.ts.map +1 -1
  6. package/build/api/pin.js +17 -0
  7. package/build/api/top-langs.d.ts.map +1 -1
  8. package/build/api/top-langs.js +36 -4
  9. package/build/api/wakatime.d.ts.map +1 -1
  10. package/build/api/wakatime.js +34 -0
  11. package/build/calculateRank.d.ts +12 -11
  12. package/build/calculateRank.d.ts.map +1 -1
  13. package/build/calculateRank.js +18 -18
  14. package/build/cards/repo.d.ts.map +1 -1
  15. package/build/cards/repo.js +29 -20
  16. package/build/cards/stats.d.ts +4 -2
  17. package/build/cards/stats.d.ts.map +1 -1
  18. package/build/cards/stats.js +21 -8
  19. package/build/cards/top-languages.d.ts.map +1 -1
  20. package/build/cards/top-languages.js +39 -11
  21. package/build/cards/wakatime.d.ts.map +1 -1
  22. package/build/cards/wakatime.js +29 -8
  23. package/build/common/Card.d.ts +9 -3
  24. package/build/common/Card.d.ts.map +1 -1
  25. package/build/common/Card.js +47 -19
  26. package/build/common/color.d.ts +42 -8
  27. package/build/common/color.d.ts.map +1 -1
  28. package/build/common/color.js +62 -13
  29. package/build/common/html.js +1 -1
  30. package/build/common/http.d.ts +13 -4
  31. package/build/common/http.d.ts.map +1 -1
  32. package/build/common/http.js +3 -3
  33. package/build/common/languageColors.json +9 -1
  34. package/build/common/ops.d.ts +30 -29
  35. package/build/common/ops.d.ts.map +1 -1
  36. package/build/common/ops.js +32 -37
  37. package/build/common/render.d.ts +115 -111
  38. package/build/common/render.d.ts.map +1 -1
  39. package/build/common/render.js +138 -87
  40. package/build/common/retryer.d.ts +14 -6
  41. package/build/common/retryer.d.ts.map +1 -1
  42. package/build/common/retryer.js +1 -0
  43. package/build/fetchers/gist.d.ts +6 -21
  44. package/build/fetchers/gist.d.ts.map +1 -1
  45. package/build/fetchers/gist.js +30 -35
  46. package/build/fetchers/repo.js +1 -1
  47. package/build/fetchers/stats.d.ts.map +1 -1
  48. package/build/fetchers/stats.js +0 -1
  49. package/build/fetchers/types.d.ts +126 -0
  50. package/build/fetchers/types.d.ts.map +1 -0
  51. package/build/fetchers/types.js +1 -0
  52. package/package.json +4 -4
  53. package/src/_emoji-name-map.d.ts +10 -0
  54. package/src/api/gist.js +36 -0
  55. package/src/api/index.js +19 -0
  56. package/src/api/pin.js +18 -0
  57. package/src/api/top-langs.js +38 -4
  58. package/src/api/wakatime.js +36 -0
  59. package/src/{calculateRank.js → calculateRank.ts} +29 -19
  60. package/src/cards/repo.js +30 -20
  61. package/src/cards/stats.js +22 -8
  62. package/src/cards/top-languages.js +49 -11
  63. package/src/cards/wakatime.js +33 -8
  64. package/src/common/Card.ts +51 -16
  65. package/src/common/color.ts +85 -22
  66. package/src/common/html.ts +1 -1
  67. package/src/common/http.ts +31 -0
  68. package/src/common/languageColors.json +9 -1
  69. package/src/common/{ops.js → ops.ts} +48 -50
  70. package/src/common/{render.js → render.ts} +208 -96
  71. package/src/common/retryer.ts +18 -10
  72. package/src/fetchers/gist.ts +136 -0
  73. package/src/fetchers/repo.js +1 -1
  74. package/src/fetchers/stats.js +0 -1
  75. package/src/common/http.js +0 -19
  76. package/src/fetchers/gist.js +0 -112
  77. /package/src/fetchers/{types.d.ts → types.ts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"gist.d.ts","sourceRoot":"","sources":["../../src/api/gist.js"],"names":[],"mappings":"AAWe;;;;;;;;;;;;;;;;GAqFd"}
1
+ {"version":3,"file":"gist.d.ts","sourceRoot":"","sources":["../../src/api/gist.js"],"names":[],"mappings":"AAYe;;;;;;;;;;;;;;;;GAwHd"}
package/build/api/gist.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { renderGistCard } from "../cards/gist.js";
2
+ import { findInvalidColor } from "../common/color.js";
2
3
  import { MissingParamError, retrieveSecondaryMessage, } from "../common/error.js";
3
4
  import { parseBoolean } from "../common/ops.js";
4
5
  import { renderError } from "../common/render.js";
@@ -6,6 +7,22 @@ import { fetchGist } from "../fetchers/gist.js";
6
7
  import { isLocaleAvailable } from "../translations.js";
7
8
  // @ts-ignore
8
9
  export default async ({ id, title_color, icon_color, text_color, bg_color, theme, locale, border_radius, border_color, show_owner, browser_rendering, hide_border, }, pat = null) => {
10
+ const invalidColorInput = findInvalidColor({
11
+ title_color,
12
+ icon_color,
13
+ text_color,
14
+ bg_color,
15
+ border_color,
16
+ });
17
+ if (invalidColorInput) {
18
+ return {
19
+ status: "error - permanent",
20
+ content: renderError({
21
+ message: "Something went wrong",
22
+ secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
23
+ }),
24
+ };
25
+ }
9
26
  if (locale && !isLocaleAvailable(locale)) {
10
27
  return {
11
28
  status: "error - permanent",
@@ -22,6 +39,23 @@ export default async ({ id, title_color, icon_color, text_color, bg_color, theme
22
39
  }),
23
40
  };
24
41
  }
42
+ const safePattern = /^[-\w/.,]+$/;
43
+ if (id && !safePattern.test(id)) {
44
+ return {
45
+ status: "error - permanent",
46
+ content: renderError({
47
+ message: "Something went wrong",
48
+ secondaryMessage: "Gist ID contains unsafe characters",
49
+ renderOptions: {
50
+ title_color,
51
+ text_color,
52
+ bg_color,
53
+ border_color,
54
+ theme,
55
+ },
56
+ }),
57
+ };
58
+ }
25
59
  try {
26
60
  const gistData = await fetchGist(id, pat);
27
61
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.js"],"names":[],"mappings":"AAWe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2Kd"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.js"],"names":[],"mappings":"AAYe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6Ld"}
@@ -1,4 +1,5 @@
1
1
  import { renderStatsCard } from "../cards/stats.js";
2
+ import { findInvalidColor } from "../common/color.js";
2
3
  import { MissingParamError, retrieveSecondaryMessage, } from "../common/error.js";
3
4
  import { parseArray, parseBoolean } from "../common/ops.js";
4
5
  import { renderError } from "../common/render.js";
@@ -6,6 +7,23 @@ import { fetchStats } from "../fetchers/stats.js";
6
7
  import { isLocaleAvailable } from "../translations.js";
7
8
  // @ts-ignore
8
9
  export default async ({ username, repo, owner, hide, hide_title, hide_border, card_width, hide_rank, show_icons, include_all_commits, commits_year, line_height, title_color, ring_color, icon_color, text_color, text_bold, bg_color, theme, exclude_repo, custom_title, locale, disable_animations, border_radius, number_format, role, number_precision, border_color, rank_icon, show, }, pat = null) => {
10
+ const invalidColorInput = findInvalidColor({
11
+ title_color,
12
+ ring_color,
13
+ icon_color,
14
+ text_color,
15
+ bg_color,
16
+ border_color,
17
+ });
18
+ if (invalidColorInput) {
19
+ return {
20
+ status: "error - permanent",
21
+ content: renderError({
22
+ message: "Something went wrong",
23
+ secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
24
+ }),
25
+ };
26
+ }
9
27
  if (locale && !isLocaleAvailable(locale)) {
10
28
  return {
11
29
  status: "error - permanent",
@@ -1 +1 @@
1
- {"version":3,"file":"pin.d.ts","sourceRoot":"","sources":["../../src/api/pin.js"],"names":[],"mappings":"AAWe;;;;;;;;;;;;;;;;;;;;;;;;GAoId"}
1
+ {"version":3,"file":"pin.d.ts","sourceRoot":"","sources":["../../src/api/pin.js"],"names":[],"mappings":"AAYe;;;;;;;;;;;;;;;;;;;;;;;;GAqJd"}
package/build/api/pin.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { renderRepoCard } from "../cards/repo.js";
2
+ import { findInvalidColor } from "../common/color.js";
2
3
  import { MissingParamError, retrieveSecondaryMessage, } from "../common/error.js";
3
4
  import { parseArray, parseBoolean } from "../common/ops.js";
4
5
  import { renderError } from "../common/render.js";
@@ -6,6 +7,22 @@ import { fetchRepo } from "../fetchers/repo.js";
6
7
  import { isLocaleAvailable } from "../translations.js";
7
8
  // @ts-ignore
8
9
  export default async ({ username, repo, hide_border, title_color, icon_color, text_color, bg_color, card_width, theme, show_owner, browser_rendering, show, show_icons, number_format, text_bold, line_height, locale, border_radius, border_color, description_lines_count, }, pat = null) => {
10
+ const invalidColorInput = findInvalidColor({
11
+ title_color,
12
+ icon_color,
13
+ text_color,
14
+ bg_color,
15
+ border_color,
16
+ });
17
+ if (invalidColorInput) {
18
+ return {
19
+ status: "error - permanent",
20
+ content: renderError({
21
+ message: "Something went wrong",
22
+ secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
23
+ }),
24
+ };
25
+ }
9
26
  if (locale && !isLocaleAvailable(locale)) {
10
27
  return {
11
28
  status: "error - permanent",
@@ -1 +1 @@
1
- {"version":3,"file":"top-langs.d.ts","sourceRoot":"","sources":["../../src/api/top-langs.js"],"names":[],"mappings":"AAWe;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0Jd"}
1
+ {"version":3,"file":"top-langs.d.ts","sourceRoot":"","sources":["../../src/api/top-langs.js"],"names":[],"mappings":"AAYe;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2Ld"}
@@ -1,4 +1,5 @@
1
1
  import { renderTopLanguages } from "../cards/top-languages.js";
2
+ import { findInvalidColor } from "../common/color.js";
2
3
  import { MissingParamError, retrieveSecondaryMessage, } from "../common/error.js";
3
4
  import { parseArray, parseBoolean } from "../common/ops.js";
4
5
  import { renderError } from "../common/render.js";
@@ -6,6 +7,22 @@ import { fetchTopLanguages } from "../fetchers/top-languages.js";
6
7
  import { isLocaleAvailable } from "../translations.js";
7
8
  // @ts-ignore
8
9
  export default async ({ username, hide, hide_title, hide_border, card_width, title_color, text_color, bg_color, prog_bar_bg_color, theme, layout, langs_count, exclude_repo, size_weight, count_weight, custom_title, locale, border_radius, border_color, role, disable_animations, hide_progress, hide_values, stats_format, }, pat = null) => {
10
+ const invalidColorInput = findInvalidColor({
11
+ title_color,
12
+ text_color,
13
+ bg_color,
14
+ prog_bar_bg_color,
15
+ border_color,
16
+ });
17
+ if (invalidColorInput) {
18
+ return {
19
+ status: "error - permanent",
20
+ content: renderError({
21
+ message: "Something went wrong",
22
+ secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
23
+ }),
24
+ };
25
+ }
9
26
  if (locale && !isLocaleAvailable(locale)) {
10
27
  return {
11
28
  status: "error - permanent",
@@ -22,9 +39,25 @@ export default async ({ username, hide, hide_title, hide_border, card_width, tit
22
39
  }),
23
40
  };
24
41
  }
42
+ const safePattern = /^[-\w/.,]+$/;
43
+ if (username && !safePattern.test(username)) {
44
+ return {
45
+ status: "error - permanent",
46
+ content: renderError({
47
+ message: "Something went wrong",
48
+ secondaryMessage: "Username contains unsafe characters",
49
+ renderOptions: {
50
+ title_color,
51
+ text_color,
52
+ bg_color,
53
+ border_color,
54
+ theme,
55
+ },
56
+ }),
57
+ };
58
+ }
25
59
  if (layout !== undefined &&
26
- (typeof layout !== "string" ||
27
- !["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout))) {
60
+ !["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout)) {
28
61
  return {
29
62
  status: "error - permanent",
30
63
  content: renderError({
@@ -41,8 +74,7 @@ export default async ({ username, hide, hide_title, hide_border, card_width, tit
41
74
  };
42
75
  }
43
76
  if (stats_format !== undefined &&
44
- (typeof stats_format !== "string" ||
45
- !["bytes", "percentages"].includes(stats_format))) {
77
+ !["bytes", "percentages"].includes(stats_format)) {
46
78
  return {
47
79
  status: "error - permanent",
48
80
  content: renderError({
@@ -1 +1 @@
1
- {"version":3,"file":"wakatime.d.ts","sourceRoot":"","sources":["../../src/api/wakatime.js"],"names":[],"mappings":"AAWe;;;;;;;;;;;;;;;;;;;;;;;;;GAmGd"}
1
+ {"version":3,"file":"wakatime.d.ts","sourceRoot":"","sources":["../../src/api/wakatime.js"],"names":[],"mappings":"AAYe;;;;;;;;;;;;;;;;;;;;;;;;;GAsId"}
@@ -1,4 +1,5 @@
1
1
  import { renderWakatimeCard } from "../cards/wakatime.js";
2
+ import { findInvalidColor } from "../common/color.js";
2
3
  import { MissingParamError, retrieveSecondaryMessage, } from "../common/error.js";
3
4
  import { parseArray, parseBoolean } from "../common/ops.js";
4
5
  import { renderError } from "../common/render.js";
@@ -6,6 +7,22 @@ import { fetchWakatimeStats } from "../fetchers/wakatime.js";
6
7
  import { isLocaleAvailable } from "../translations.js";
7
8
  // @ts-ignore
8
9
  export default async ({ username, title_color, icon_color, hide_border, card_width, line_height, text_color, bg_color, theme, hide_title, hide_progress, custom_title, locale, layout, langs_count, hide, api_domain, border_radius, border_color, display_format, disable_animations, }) => {
10
+ const invalidColorInput = findInvalidColor({
11
+ title_color,
12
+ icon_color,
13
+ text_color,
14
+ bg_color,
15
+ border_color,
16
+ });
17
+ if (invalidColorInput) {
18
+ return {
19
+ status: "error - permanent",
20
+ content: renderError({
21
+ message: "Something went wrong",
22
+ secondaryMessage: `Invalid color input for parameter "${invalidColorInput}"`,
23
+ }),
24
+ };
25
+ }
9
26
  if (locale && !isLocaleAvailable(locale)) {
10
27
  return {
11
28
  status: "error - permanent",
@@ -22,6 +39,23 @@ export default async ({ username, title_color, icon_color, hide_border, card_wid
22
39
  }),
23
40
  };
24
41
  }
42
+ const safePattern = /^[-\w/.,]+$/;
43
+ if (username && !safePattern.test(username)) {
44
+ return {
45
+ status: "error - permanent",
46
+ content: renderError({
47
+ message: "Something went wrong",
48
+ secondaryMessage: "Username contains unsafe characters",
49
+ renderOptions: {
50
+ title_color,
51
+ text_color,
52
+ bg_color,
53
+ border_color,
54
+ theme,
55
+ },
56
+ }),
57
+ };
58
+ }
25
59
  try {
26
60
  const stats = await fetchWakatimeStats({ username, api_domain });
27
61
  return {
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Calculates the users rank.
3
3
  *
4
- * @param {object} params Parameters on which the user's rank depends.
5
- * @param {boolean} params.all_commits Whether `include_all_commits` was used.
6
- * @param {number} params.commits Number of commits.
7
- * @param {number} params.prs The number of pull requests.
8
- * @param {number} params.issues The number of issues.
9
- * @param {number} params.reviews The number of reviews.
10
- * @param {number} params.repos Total number of repos.
11
- * @param {number} params.stars The number of stars.
12
- * @param {number} params.followers The number of followers.
13
- * @returns {{ level: string, percentile: number }} The users rank.
4
+ * @param params Parameters on which the user's rank depends.
5
+ * @param params.all_commits Whether `include_all_commits` was used.
6
+ * @param params.commits Number of commits.
7
+ * @param params.prs The number of pull requests.
8
+ * @param params.issues The number of issues.
9
+ * @param params.reviews The number of reviews.
10
+ * @param params.repos Total number of repos (accepted for compatibility, unused in the calculation).
11
+ * @param params.stars The number of stars.
12
+ * @param params.followers The number of followers.
13
+ * @returns The users rank.
14
14
  */
15
- export function calculateRank({ all_commits, commits, prs, issues, reviews, repos, stars, followers, }: {
15
+ declare function calculateRank({ all_commits, commits, prs, issues, reviews, stars, followers, }: {
16
16
  all_commits: boolean;
17
17
  commits: number;
18
18
  prs: number;
@@ -25,4 +25,5 @@ export function calculateRank({ all_commits, commits, prs, issues, reviews, repo
25
25
  level: string;
26
26
  percentile: number;
27
27
  };
28
+ export { calculateRank };
28
29
  //# sourceMappingURL=calculateRank.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"calculateRank.d.ts","sourceRoot":"","sources":["../src/calculateRank.js"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;GAaG;AACH,wGAVG;IAAwB,WAAW,EAA3B,OAAO;IACQ,OAAO,EAAtB,MAAM;IACS,GAAG,EAAlB,MAAM;IACS,MAAM,EAArB,MAAM;IACS,OAAO,EAAtB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,SAAS,EAAxB,MAAM;CACd,GAAU;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAkDjD"}
1
+ {"version":3,"file":"calculateRank.d.ts","sourceRoot":"","sources":["../src/calculateRank.ts"],"names":[],"mappings":"AAqBA;;;;;;;;;;;;;GAaG;AACH,iBAAS,aAAa,CAAC,EACrB,WAAW,EACX,OAAO,EACP,GAAG,EACH,MAAM,EACN,OAAO,EACP,KAAK,EACL,SAAS,GACV,EAAE;IACD,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAyCxC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Calculates the exponential cdf.
3
3
  *
4
- * @param {number} x The value.
5
- * @returns {number} The exponential cdf.
4
+ * @param x The value.
5
+ * @returns The exponential cdf.
6
6
  */
7
7
  function exponential_cdf(x) {
8
8
  return 1 - 2 ** -x;
@@ -10,8 +10,8 @@ function exponential_cdf(x) {
10
10
  /**
11
11
  * Calculates the log normal cdf.
12
12
  *
13
- * @param {number} x The value.
14
- * @returns {number} The log normal cdf.
13
+ * @param x The value.
14
+ * @returns The log normal cdf.
15
15
  */
16
16
  function log_normal_cdf(x) {
17
17
  // approximation
@@ -20,21 +20,18 @@ function log_normal_cdf(x) {
20
20
  /**
21
21
  * Calculates the users rank.
22
22
  *
23
- * @param {object} params Parameters on which the user's rank depends.
24
- * @param {boolean} params.all_commits Whether `include_all_commits` was used.
25
- * @param {number} params.commits Number of commits.
26
- * @param {number} params.prs The number of pull requests.
27
- * @param {number} params.issues The number of issues.
28
- * @param {number} params.reviews The number of reviews.
29
- * @param {number} params.repos Total number of repos.
30
- * @param {number} params.stars The number of stars.
31
- * @param {number} params.followers The number of followers.
32
- * @returns {{ level: string, percentile: number }} The users rank.
23
+ * @param params Parameters on which the user's rank depends.
24
+ * @param params.all_commits Whether `include_all_commits` was used.
25
+ * @param params.commits Number of commits.
26
+ * @param params.prs The number of pull requests.
27
+ * @param params.issues The number of issues.
28
+ * @param params.reviews The number of reviews.
29
+ * @param params.repos Total number of repos (accepted for compatibility, unused in the calculation).
30
+ * @param params.stars The number of stars.
31
+ * @param params.followers The number of followers.
32
+ * @returns The users rank.
33
33
  */
34
- function calculateRank({ all_commits, commits, prs, issues, reviews,
35
- // eslint-disable-next-line no-unused-vars
36
- repos, // unused
37
- stars, followers, }) {
34
+ function calculateRank({ all_commits, commits, prs, issues, reviews, stars, followers, }) {
38
35
  const COMMITS_MEDIAN = all_commits ? 1000 : 250, COMMITS_WEIGHT = 2;
39
36
  const PRS_MEDIAN = 50, PRS_WEIGHT = 3;
40
37
  const ISSUES_MEDIAN = 25, ISSUES_WEIGHT = 1;
@@ -58,6 +55,9 @@ stars, followers, }) {
58
55
  FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) /
59
56
  TOTAL_WEIGHT;
60
57
  const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)];
58
+ if (level === undefined) {
59
+ throw new Error("Unable to determine rank level");
60
+ }
61
61
  return { level, percentile: rank * 100 };
62
62
  }
63
63
  export { calculateRank };
@@ -1 +1 @@
1
- {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/cards/repo.js"],"names":[],"mappings":";;;;;;;;AAiDA;;;GAGG;AAEH;;;;;;GAMG;AACH,qCAJW,cAAc,YACd,OAAO,CAAC,eAAe,CAAC,GACtB,MAAM,CA6SlB"}
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/cards/repo.js"],"names":[],"mappings":";;;;;;;;AA0DA;;;GAGG;AAEH;;;;;;GAMG;AACH,qCAJW,cAAc,YACd,OAAO,CAAC,eAAe,CAAC,GACtB,MAAM,CA8SlB"}
@@ -1,6 +1,6 @@
1
1
  import { Card } from "../common/Card.js";
2
2
  import { I18n } from "../common/I18n.js";
3
- import { getCardColors } from "../common/color.js";
3
+ import { getCardColors, isPrefixedHexColor } from "../common/color.js";
4
4
  import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
5
5
  import { encodeHTML } from "../common/html.js";
6
6
  import { icons } from "../common/icons.js";
@@ -21,20 +21,28 @@ const DESCRIPTION_MAX_LINES = 3;
21
21
  * @param {string} textColor The color of the text.
22
22
  * @returns {string} Wrapped repo description SVG object.
23
23
  */
24
- const getBadgeSVG = (label, textColor, xOffset = 0) => `
25
- <g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -18)">
26
- <rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
27
- <text
28
- x="23" y="-5"
29
- alignment-baseline="central"
30
- dominant-baseline="central"
31
- text-anchor="middle"
32
- fill="${textColor}"
33
- >
34
- ${label}
35
- </text>
36
- </g>
37
- `;
24
+ const getBadgeSVG = (label, textColor, xOffset = 0) => {
25
+ if (!isPrefixedHexColor(textColor)) {
26
+ throw new Error(`Invalid text color: "${textColor}"`);
27
+ }
28
+ if (!Number.isFinite(xOffset)) {
29
+ throw new Error(`Invalid xOffset: "${xOffset}"`);
30
+ }
31
+ return `
32
+ <g data-testid="badge" class="badge" transform="translate(${320 + xOffset}, -18)">
33
+ <rect stroke="${textColor}" stroke-width="1" width="70" height="20" x="-12" y="-14" ry="10" rx="10"></rect>
34
+ <text
35
+ x="23" y="-5"
36
+ alignment-baseline="central"
37
+ dominant-baseline="central"
38
+ text-anchor="middle"
39
+ fill="${textColor}"
40
+ >
41
+ ${encodeHTML(label)}
42
+ </text>
43
+ </g>
44
+ `;
45
+ };
38
46
  /**
39
47
  * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data.
40
48
  * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options.
@@ -59,6 +67,7 @@ const renderRepoCard = (repo, options = {}) => {
59
67
  translations: repoCardLocales,
60
68
  });
61
69
  let repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], []));
70
+ const encodedUsername = encodeURIComponent(username);
62
71
  const STATS = {};
63
72
  if (show.includes("prs_authored")) {
64
73
  STATS.prs_authored = {
@@ -66,7 +75,7 @@ const renderRepoCard = (repo, options = {}) => {
66
75
  label: i18n.t("repocard.prs-authored"),
67
76
  value: totalPRsAuthored,
68
77
  id: "prs_authored",
69
- link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=pullrequests`,
78
+ link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=pullrequests`,
70
79
  };
71
80
  }
72
81
  if (show.includes("prs_commented")) {
@@ -75,7 +84,7 @@ const renderRepoCard = (repo, options = {}) => {
75
84
  label: i18n.t("repocard.prs-commented"),
76
85
  value: totalPRsCommented,
77
86
  id: "prs_commented",
78
- link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=pullrequests`,
87
+ link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
79
88
  };
80
89
  }
81
90
  if (show.includes("prs_reviewed")) {
@@ -84,7 +93,7 @@ const renderRepoCard = (repo, options = {}) => {
84
93
  label: i18n.t("repocard.prs-reviewed"),
85
94
  value: totalPRsReviewed,
86
95
  id: "prs_reviewed",
87
- link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&amp;type=pullrequests`,
96
+ link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
88
97
  };
89
98
  }
90
99
  if (show.includes("issues_authored")) {
@@ -93,7 +102,7 @@ const renderRepoCard = (repo, options = {}) => {
93
102
  label: i18n.t("repocard.issues-authored"),
94
103
  value: totalIssuesAuthored,
95
104
  id: "issues_authored",
96
- link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=issues`,
105
+ link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=issues`,
97
106
  };
98
107
  }
99
108
  if (show.includes("issues_commented")) {
@@ -102,7 +111,7 @@ const renderRepoCard = (repo, options = {}) => {
102
111
  label: i18n.t("repocard.issues-commented"),
103
112
  value: totalIssuesCommented,
104
113
  id: "issues_commented",
105
- link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=issues`,
114
+ link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=issues`,
106
115
  };
107
116
  }
108
117
  const statItems = Object.keys(STATS).map((key, index) =>
@@ -15,8 +15,10 @@ export function renderStatsCard(stats: StatsData, options: Partial<StatCardOptio
15
15
  /**
16
16
  * Create a stats card text item.
17
17
  *
18
+ * The caller must ensure that the passed `icon` and `link` are properly sanitized!
19
+ *
18
20
  * @param {object} params Object that contains the createTextNode parameters.
19
- * @param {string} params.icon The icon to display.
21
+ * @param {string} params.icon The sanitized icon to display.
20
22
  * @param {string} params.label The label to display.
21
23
  * @param {number} params.value The value to display.
22
24
  * @param {string} params.id The id of the stat.
@@ -27,7 +29,7 @@ export function renderStatsCard(stats: StatsData, options: Partial<StatCardOptio
27
29
  * @param {boolean} params.bold Whether to bold the label.
28
30
  * @param {string} params.numberFormat The format of numbers on card.
29
31
  * @param {number=} params.numberPrecision The precision of numbers on card.
30
- * @param {string} params.link Url to link to.
32
+ * @param {string} params.link Sanitized url to link to.
31
33
  * @param {number} params.labelXOffset horizontal offset for label.
32
34
  * @returns {string} The stats card text item SVG object.
33
35
  */
@@ -1 +1 @@
1
- {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cards/stats.js"],"names":[],"mappings":";;AA4PA;;;GAGG;AAEH;;;;;;GAMG;AACH,uCAJW,SAAS,WACT,OAAO,CAAC,eAAe,CAAC,+CACtB,MAAM,CAsZlB;AAzmBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,kKAfG;IAAuB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,EAAE,EAAjB,MAAM;IACU,UAAU,GAA1B,MAAM,YAAC;IACQ,KAAK,EAApB,MAAM;IACU,SAAS,EAAzB,OAAO;IACQ,aAAa,EAA5B,MAAM;IACU,IAAI,EAApB,OAAO;IACQ,YAAY,EAA3B,MAAM;IACU,eAAe,GAA/B,MAAM,YAAC;IACQ,IAAI,EAAnB,MAAM;IACS,YAAY,EAA3B,MAAM;CACd,GAAU,MAAM,CAuDlB"}
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/cards/stats.js"],"names":[],"mappings":";;AAyQA;;;GAGG;AAEH;;;;;;GAMG;AACH,uCAJW,SAAS,WACT,OAAO,CAAC,eAAe,CAAC,+CACtB,MAAM,CAuZlB;AAtnBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,kKAfG;IAAuB,IAAI,EAAnB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,KAAK,EAApB,MAAM;IACS,EAAE,EAAjB,MAAM;IACU,UAAU,GAA1B,MAAM,YAAC;IACQ,KAAK,EAApB,MAAM;IACU,SAAS,EAAzB,OAAO;IACQ,aAAa,EAA5B,MAAM;IACU,IAAI,EAApB,OAAO;IACQ,YAAY,EAA3B,MAAM;IACU,eAAe,GAA/B,MAAM,YAAC;IACQ,IAAI,EAAnB,MAAM;IACS,YAAY,EAA3B,MAAM;CACd,GAAU,MAAM,CAiElB"}
@@ -3,6 +3,7 @@ import { I18n } from "../common/I18n.js";
3
3
  import { getCardColors } from "../common/color.js";
4
4
  import { CustomError } from "../common/error.js";
5
5
  import { kFormatter } from "../common/fmt.js";
6
+ import { encodeHTML } from "../common/html.js";
6
7
  import { icons, rankIcon } from "../common/icons.js";
7
8
  import { buildSearchFilter, clampValue } from "../common/ops.js";
8
9
  import { flexLayout, measureText } from "../common/render.js";
@@ -49,8 +50,10 @@ const LONG_LOCALES = [
49
50
  /**
50
51
  * Create a stats card text item.
51
52
  *
53
+ * The caller must ensure that the passed `icon` and `link` are properly sanitized!
54
+ *
52
55
  * @param {object} params Object that contains the createTextNode parameters.
53
- * @param {string} params.icon The icon to display.
56
+ * @param {string} params.icon The sanitized icon to display.
54
57
  * @param {string} params.label The label to display.
55
58
  * @param {number} params.value The value to display.
56
59
  * @param {string} params.id The id of the stat.
@@ -61,11 +64,20 @@ const LONG_LOCALES = [
61
64
  * @param {boolean} params.bold Whether to bold the label.
62
65
  * @param {string} params.numberFormat The format of numbers on card.
63
66
  * @param {number=} params.numberPrecision The precision of numbers on card.
64
- * @param {string} params.link Url to link to.
67
+ * @param {string} params.link Sanitized url to link to.
65
68
  * @param {number} params.labelXOffset horizontal offset for label.
66
69
  * @returns {string} The stats card text item SVG object.
67
70
  */
68
71
  const createTextNode = ({ icon, label, value, id, unitSymbol, index, showIcons, shiftValuePos, bold, numberFormat, numberPrecision, link, labelXOffset = 25, }) => {
72
+ if (!Number.isFinite(labelXOffset)) {
73
+ throw new Error(`Invalid labelXOffset: "${labelXOffset}"`);
74
+ }
75
+ if (!Number.isFinite(shiftValuePos)) {
76
+ throw new Error(`Invalid shiftValuePos: "${shiftValuePos}"`);
77
+ }
78
+ if (!Number.isFinite(index)) {
79
+ throw new Error(`Invalid index: "${index}"`);
80
+ }
69
81
  const precision = typeof numberPrecision === "number" && !isNaN(numberPrecision)
70
82
  ? clampValue(numberPrecision, 0, 2)
71
83
  : undefined;
@@ -86,7 +98,7 @@ const createTextNode = ({ icon, label, value, id, unitSymbol, index, showIcons,
86
98
  (link ? `<a href="${link}">` : "") +
87
99
  `
88
100
  ${iconSvg}
89
- <text class="stat ${bold ? " bold" : "not_bold"}" ${labelOffset} y="12.5">${label}:</text>
101
+ <text class="stat ${bold ? " bold" : "not_bold"}" ${labelOffset} y="12.5">${encodeHTML(label)}:</text>
90
102
  <text
91
103
  class="stat ${bold ? " bold" : "not_bold"}"
92
104
  x="${(showIcons ? 140 : 120) + (bold ? 5 : 0) + shiftValuePos}"
@@ -315,13 +327,14 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
315
327
  };
316
328
  }
317
329
  let repoFilter = encodeURIComponent(buildSearchFilter(repo, owner));
330
+ const encodedUsername = encodeURIComponent(username);
318
331
  if (show.includes("prs_authored")) {
319
332
  STATS.prs_authored = {
320
333
  icon: icons.prs,
321
334
  label: i18n.t("statcard.prs-authored"),
322
335
  value: totalPRsAuthored,
323
336
  id: "prs_authored",
324
- link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=pullrequests`,
337
+ link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=pullrequests`,
325
338
  };
326
339
  }
327
340
  if (show.includes("prs_commented")) {
@@ -330,7 +343,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
330
343
  label: i18n.t("statcard.prs-commented"),
331
344
  value: totalPRsCommented,
332
345
  id: "prs_commented",
333
- link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=pullrequests`,
346
+ link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
334
347
  };
335
348
  }
336
349
  if (show.includes("prs_reviewed")) {
@@ -339,7 +352,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
339
352
  label: i18n.t("statcard.prs-reviewed"),
340
353
  value: totalPRsReviewed,
341
354
  id: "prs_reviewed",
342
- link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&amp;type=pullrequests`,
355
+ link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=pullrequests`,
343
356
  };
344
357
  }
345
358
  if (show.includes("issues_authored")) {
@@ -348,7 +361,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
348
361
  label: i18n.t("statcard.issues-authored"),
349
362
  value: totalIssuesAuthored,
350
363
  id: "issues_authored",
351
- link: `https://github.com/search?q=${repoFilter}author%3A${username}&amp;type=issues`,
364
+ link: `https://github.com/search?q=${repoFilter}author%3A${encodedUsername}&amp;type=issues`,
352
365
  };
353
366
  }
354
367
  if (show.includes("issues_commented")) {
@@ -357,7 +370,7 @@ const renderStatsCard = (stats, options = {}, username, repo = [], owner = []) =
357
370
  label: i18n.t("statcard.issues-commented"),
358
371
  value: totalIssuesCommented,
359
372
  id: "issues_commented",
360
- link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&amp;type=issues`,
373
+ link: `https://github.com/search?q=${repoFilter}commenter%3A${encodedUsername}+-author%3A${encodedUsername}&amp;type=issues`,
361
374
  };
362
375
  }
363
376
  STATS.contribs = {
@@ -1 +1 @@
1
- {"version":3,"file":"top-languages.d.ts","sourceRoot":"","sources":["../../src/cards/top-languages.js"],"names":[],"mappings":";;qBAowBa,cAAc,CAAC,QAAQ,CAAC;;AA3uBrC;;GAEG;AAEH;;;;;GAKG;AACH,oCAHW,IAAI,EAAE,GACJ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAOvD;AAEH;;;;;GAKG;AACH,iDAHW,MAAM,GACJ,MAAM,CAE4D;AAE/E;;;;;GAKG;AACH,iDAHW,MAAM,GACJ,MAAM,CAE4D;AAE/E;;;;;;;;GAQG;AACH,0CANW,MAAM,WACN,MAAM,UACN,MAAM,kBACN,MAAM,GACJ;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAC,CAQlC;AAED;;;;;;;;GAQG;AACH,0CANW,MAAM,WACN,MAAM,KACN,MAAM,KACN,MAAM,GACJ;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAC,CASpD;AAED;;;;;GAKG;AACH,wCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,yDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,wDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,uDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,+DAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,qDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;GAOG;AACH,2CALW,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,eACpB,MAAM,SACN,MAAM,EAAE,YAAC,GACP;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CA4BxD;AA0mBD;;GAEG;AAEH;;;;;;GAMG;AACH,6CAJW,WAAW,YACX,OAAO,CAAC,cAAc,CAAC,GACrB,MAAM,CAkLlB;AA59BD,6BAAuB,GAAG,CAAC;AA2wB3B;;;;;;;GAOG;AACH,4EAJG;IAAuB,MAAM,GAArB,MAAM,YAAC;IACS,aAAa,GAA7B,OAAO,YAAC;CAChB,GAAU,MAAM,CAclB"}
1
+ {"version":3,"file":"top-languages.d.ts","sourceRoot":"","sources":["../../src/cards/top-languages.js"],"names":[],"mappings":";;qBAsyBa,cAAc,CAAC,QAAQ,CAAC;;AAxwBrC;;GAEG;AAEH;;;;;GAKG;AACH,oCAHW,IAAI,EAAE,GACJ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAOvD;AAEH;;;;;GAKG;AACH,iDAHW,MAAM,GACJ,MAAM,CAE4D;AAE/E;;;;;GAKG;AACH,iDAHW,MAAM,GACJ,MAAM,CAE4D;AAE/E;;;;;;;;GAQG;AACH,0CANW,MAAM,WACN,MAAM,UACN,MAAM,kBACN,MAAM,GACJ;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAC,CAQlC;AAED;;;;;;;;GAQG;AACH,0CANW,MAAM,WACN,MAAM,KACN,MAAM,KACN,MAAM,GACJ;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAC,CASpD;AAED;;;;;GAKG;AACH,wCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,yDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,wDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,uDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,+DAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,qDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;GAIG;AACH,mDAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;GAOG;AACH,2CALW,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,eACpB,MAAM,SACN,MAAM,EAAE,YAAC,GACP;IAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,CA4BxD;AA2oBD;;GAEG;AAEH;;;;;;GAMG;AACH,6CAJW,WAAW,YACX,OAAO,CAAC,cAAc,CAAC,GACrB,MAAM,CAkLlB;AA7/BD,6BAAuB,GAAG,CAAC;AA4yB3B;;;;;;;GAOG;AACH,4EAJG;IAAuB,MAAM,GAArB,MAAM,YAAC;IACS,aAAa,GAA7B,OAAO,YAAC;CAChB,GAAU,MAAM,CAclB"}